Revert "Move frameworks/base/tools/ to frameworks/tools/"

This reverts commit 9f6a119c8aa276432ece4fe2118bd8a3c9b1067e.
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
new file mode 100644
index 0000000..3797b49
--- /dev/null
+++ b/tools/aapt/AaptAssets.cpp
@@ -0,0 +1,2689 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+
+#include "AaptAssets.h"
+#include "ResourceFilter.h"
+#include "Main.h"
+
+#include <utils/misc.h>
+#include <utils/SortedVector.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+
+static const char* kDefaultLocale = "default";
+static const char* kWildcardName = "any";
+static const char* kAssetDir = "assets";
+static const char* kResourceDir = "res";
+static const char* kValuesDir = "values";
+static const char* kMipmapDir = "mipmap";
+static const char* kInvalidChars = "/\\:";
+static const size_t kMaxAssetFileName = 100;
+
+static const String8 kResString(kResourceDir);
+
+/*
+ * Names of asset files must meet the following criteria:
+ *
+ *  - the filename length must be less than kMaxAssetFileName bytes long
+ *    (and can't be empty)
+ *  - all characters must be 7-bit printable ASCII
+ *  - none of { '/' '\\' ':' }
+ *
+ * Pass in just the filename, not the full path.
+ */
+static bool validateFileName(const char* fileName)
+{
+    const char* cp = fileName;
+    size_t len = 0;
+
+    while (*cp != '\0') {
+        if ((*cp & 0x80) != 0)
+            return false;           // reject high ASCII
+        if (*cp < 0x20 || *cp >= 0x7f)
+            return false;           // reject control chars and 0x7f
+        if (strchr(kInvalidChars, *cp) != NULL)
+            return false;           // reject path sep chars
+        cp++;
+        len++;
+    }
+
+    if (len < 1 || len > kMaxAssetFileName)
+        return false;               // reject empty or too long
+
+    return true;
+}
+
+// The default to use if no other ignore pattern is defined.
+const char * const gDefaultIgnoreAssets =
+    "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~";
+// The ignore pattern that can be passed via --ignore-assets in Main.cpp
+const char * gUserIgnoreAssets = NULL;
+
+static bool isHidden(const char *root, const char *path)
+{
+    // Patterns syntax:
+    // - Delimiter is :
+    // - Entry can start with the flag ! to avoid printing a warning
+    //   about the file being ignored.
+    // - Entry can have the flag "<dir>" to match only directories
+    //   or <file> to match only files. Default is to match both.
+    // - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
+    //   where prefix/suffix must have at least 1 character (so that
+    //   we don't match a '*' catch-all pattern.)
+    // - The special filenames "." and ".." are always ignored.
+    // - Otherwise the full string is matched.
+    // - match is not case-sensitive.
+
+    if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) {
+        return true;
+    }
+
+    const char *delim = ":";
+    const char *p = gUserIgnoreAssets;
+    if (!p || !p[0]) {
+        p = getenv("ANDROID_AAPT_IGNORE");
+    }
+    if (!p || !p[0]) {
+        p = gDefaultIgnoreAssets;
+    }
+    char *patterns = strdup(p);
+
+    bool ignore = false;
+    bool chatty = true;
+    char *matchedPattern = NULL;
+
+    String8 fullPath(root);
+    fullPath.appendPath(path);
+    FileType type = getFileType(fullPath);
+
+    int plen = strlen(path);
+
+    // Note: we don't have strtok_r under mingw.
+    for(char *token = strtok(patterns, delim);
+            !ignore && token != NULL;
+            token = strtok(NULL, delim)) {
+        chatty = token[0] != '!';
+        if (!chatty) token++; // skip !
+        if (strncasecmp(token, "<dir>" , 5) == 0) {
+            if (type != kFileTypeDirectory) continue;
+            token += 5;
+        }
+        if (strncasecmp(token, "<file>", 6) == 0) {
+            if (type != kFileTypeRegular) continue;
+            token += 6;
+        }
+
+        matchedPattern = token;
+        int n = strlen(token);
+
+        if (token[0] == '*') {
+            // Match *suffix
+            token++;
+            n--;
+            if (n <= plen) {
+                ignore = strncasecmp(token, path + plen - n, n) == 0;
+            }
+        } else if (n > 1 && token[n - 1] == '*') {
+            // Match prefix*
+            ignore = strncasecmp(token, path, n - 1) == 0;
+        } else {
+            ignore = strcasecmp(token, path) == 0;
+        }
+    }
+
+    if (ignore && chatty) {
+        fprintf(stderr, "    (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n",
+                type == kFileTypeDirectory ? "dir" : "file",
+                path,
+                matchedPattern ? matchedPattern : "");
+    }
+
+    free(patterns);
+    return ignore;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t
+AaptGroupEntry::parseNamePart(const String8& part, int* axis, uint32_t* value)
+{
+    ResTable_config config;
+
+    // IMSI - MCC
+    if (getMccName(part.string(), &config)) {
+        *axis = AXIS_MCC;
+        *value = config.mcc;
+        return 0;
+    }
+
+    // IMSI - MNC
+    if (getMncName(part.string(), &config)) {
+        *axis = AXIS_MNC;
+        *value = config.mnc;
+        return 0;
+    }
+
+    // locale - language
+    if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) {
+        *axis = AXIS_LANGUAGE;
+        *value = part[1] << 8 | part[0];
+        return 0;
+    }
+
+    // locale - language_REGION
+    if (part.length() == 5 && isalpha(part[0]) && isalpha(part[1])
+            && part[2] == '_' && isalpha(part[3]) && isalpha(part[4])) {
+        *axis = AXIS_LANGUAGE;
+        *value = (part[4] << 24) | (part[3] << 16) | (part[1] << 8) | (part[0]);
+        return 0;
+    }
+
+    // layout direction
+    if (getLayoutDirectionName(part.string(), &config)) {
+        *axis = AXIS_LAYOUTDIR;
+        *value = (config.screenLayout&ResTable_config::MASK_LAYOUTDIR);
+        return 0;
+    }
+
+    // smallest screen dp width
+    if (getSmallestScreenWidthDpName(part.string(), &config)) {
+        *axis = AXIS_SMALLESTSCREENWIDTHDP;
+        *value = config.smallestScreenWidthDp;
+        return 0;
+    }
+
+    // screen dp width
+    if (getScreenWidthDpName(part.string(), &config)) {
+        *axis = AXIS_SCREENWIDTHDP;
+        *value = config.screenWidthDp;
+        return 0;
+    }
+
+    // screen dp height
+    if (getScreenHeightDpName(part.string(), &config)) {
+        *axis = AXIS_SCREENHEIGHTDP;
+        *value = config.screenHeightDp;
+        return 0;
+    }
+
+    // screen layout size
+    if (getScreenLayoutSizeName(part.string(), &config)) {
+        *axis = AXIS_SCREENLAYOUTSIZE;
+        *value = (config.screenLayout&ResTable_config::MASK_SCREENSIZE);
+        return 0;
+    }
+
+    // screen layout long
+    if (getScreenLayoutLongName(part.string(), &config)) {
+        *axis = AXIS_SCREENLAYOUTLONG;
+        *value = (config.screenLayout&ResTable_config::MASK_SCREENLONG);
+        return 0;
+    }
+
+    // orientation
+    if (getOrientationName(part.string(), &config)) {
+        *axis = AXIS_ORIENTATION;
+        *value = config.orientation;
+        return 0;
+    }
+
+    // ui mode type
+    if (getUiModeTypeName(part.string(), &config)) {
+        *axis = AXIS_UIMODETYPE;
+        *value = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
+        return 0;
+    }
+
+    // ui mode night
+    if (getUiModeNightName(part.string(), &config)) {
+        *axis = AXIS_UIMODENIGHT;
+        *value = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
+        return 0;
+    }
+
+    // density
+    if (getDensityName(part.string(), &config)) {
+        *axis = AXIS_DENSITY;
+        *value = config.density;
+        return 0;
+    }
+
+    // touchscreen
+    if (getTouchscreenName(part.string(), &config)) {
+        *axis = AXIS_TOUCHSCREEN;
+        *value = config.touchscreen;
+        return 0;
+    }
+
+    // keyboard hidden
+    if (getKeysHiddenName(part.string(), &config)) {
+        *axis = AXIS_KEYSHIDDEN;
+        *value = config.inputFlags;
+        return 0;
+    }
+
+    // keyboard
+    if (getKeyboardName(part.string(), &config)) {
+        *axis = AXIS_KEYBOARD;
+        *value = config.keyboard;
+        return 0;
+    }
+
+    // navigation hidden
+    if (getNavHiddenName(part.string(), &config)) {
+        *axis = AXIS_NAVHIDDEN;
+        *value = config.inputFlags;
+        return 0;
+    }
+
+    // navigation
+    if (getNavigationName(part.string(), &config)) {
+        *axis = AXIS_NAVIGATION;
+        *value = config.navigation;
+        return 0;
+    }
+
+    // screen size
+    if (getScreenSizeName(part.string(), &config)) {
+        *axis = AXIS_SCREENSIZE;
+        *value = config.screenSize;
+        return 0;
+    }
+
+    // version
+    if (getVersionName(part.string(), &config)) {
+        *axis = AXIS_VERSION;
+        *value = config.version;
+        return 0;
+    }
+
+    return 1;
+}
+
+uint32_t
+AaptGroupEntry::getConfigValueForAxis(const ResTable_config& config, int axis)
+{
+    switch (axis) {
+        case AXIS_MCC:
+            return config.mcc;
+        case AXIS_MNC:
+            return config.mnc;
+        case AXIS_LANGUAGE:
+            return (((uint32_t)config.country[1]) << 24) | (((uint32_t)config.country[0]) << 16)
+                | (((uint32_t)config.language[1]) << 8) | (config.language[0]);
+        case AXIS_LAYOUTDIR:
+            return config.screenLayout&ResTable_config::MASK_LAYOUTDIR;
+        case AXIS_SCREENLAYOUTSIZE:
+            return config.screenLayout&ResTable_config::MASK_SCREENSIZE;
+        case AXIS_ORIENTATION:
+            return config.orientation;
+        case AXIS_UIMODETYPE:
+            return (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
+        case AXIS_UIMODENIGHT:
+            return (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
+        case AXIS_DENSITY:
+            return config.density;
+        case AXIS_TOUCHSCREEN:
+            return config.touchscreen;
+        case AXIS_KEYSHIDDEN:
+            return config.inputFlags;
+        case AXIS_KEYBOARD:
+            return config.keyboard;
+        case AXIS_NAVIGATION:
+            return config.navigation;
+        case AXIS_SCREENSIZE:
+            return config.screenSize;
+        case AXIS_SMALLESTSCREENWIDTHDP:
+            return config.smallestScreenWidthDp;
+        case AXIS_SCREENWIDTHDP:
+            return config.screenWidthDp;
+        case AXIS_SCREENHEIGHTDP:
+            return config.screenHeightDp;
+        case AXIS_VERSION:
+            return config.version;
+    }
+    return 0;
+}
+
+bool
+AaptGroupEntry::configSameExcept(const ResTable_config& config,
+        const ResTable_config& otherConfig, int axis)
+{
+    for (int i=AXIS_START; i<=AXIS_END; i++) {
+        if (i == axis) {
+            continue;
+        }
+        if (getConfigValueForAxis(config, i) != getConfigValueForAxis(otherConfig, i)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool
+AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
+{
+    mParamsChanged = true;
+
+    Vector<String8> parts;
+
+    String8 mcc, mnc, loc, layoutsize, layoutlong, orient, den;
+    String8 touch, key, keysHidden, nav, navHidden, size, layoutDir, vers;
+    String8 uiModeType, uiModeNight, smallestwidthdp, widthdp, heightdp;
+
+    const char *p = dir;
+    const char *q;
+    while (NULL != (q = strchr(p, '-'))) {
+        String8 val(p, q-p);
+        val.toLower();
+        parts.add(val);
+        //printf("part: %s\n", parts[parts.size()-1].string());
+        p = q+1;
+    }
+    String8 val(p);
+    val.toLower();
+    parts.add(val);
+    //printf("part: %s\n", parts[parts.size()-1].string());
+
+    const int N = parts.size();
+    int index = 0;
+    String8 part = parts[index];
+
+    // resource type
+    if (!isValidResourceType(part)) {
+        return false;
+    }
+    *resType = part;
+
+    index++;
+    if (index == N) {
+        goto success;
+    }
+    part = parts[index];
+
+    // imsi - mcc
+    if (getMccName(part.string())) {
+        mcc = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not mcc: %s\n", part.string());
+    }
+
+    // imsi - mnc
+    if (getMncName(part.string())) {
+        mnc = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not mcc: %s\n", part.string());
+    }
+
+    // locale - language
+    if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) {
+        loc = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not language: %s\n", part.string());
+    }
+
+    // locale - region
+    if (loc.length() > 0
+            && part.length() == 3 && part[0] == 'r' && part[0] && part[1]) {
+        loc += "-";
+        part.toUpper();
+        loc += part.string() + 1;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not region: %s\n", part.string());
+    }
+
+    if (getLayoutDirectionName(part.string())) {
+        layoutDir = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not layout direction: %s\n", part.string());
+    }
+
+    if (getSmallestScreenWidthDpName(part.string())) {
+        smallestwidthdp = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not smallest screen width dp: %s\n", part.string());
+    }
+
+    if (getScreenWidthDpName(part.string())) {
+        widthdp = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not screen width dp: %s\n", part.string());
+    }
+
+    if (getScreenHeightDpName(part.string())) {
+        heightdp = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not screen height dp: %s\n", part.string());
+    }
+
+    if (getScreenLayoutSizeName(part.string())) {
+        layoutsize = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not screen layout size: %s\n", part.string());
+    }
+
+    if (getScreenLayoutLongName(part.string())) {
+        layoutlong = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not screen layout long: %s\n", part.string());
+    }
+
+    // orientation
+    if (getOrientationName(part.string())) {
+        orient = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not orientation: %s\n", part.string());
+    }
+
+    // ui mode type
+    if (getUiModeTypeName(part.string())) {
+        uiModeType = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not ui mode type: %s\n", part.string());
+    }
+
+    // ui mode night
+    if (getUiModeNightName(part.string())) {
+        uiModeNight = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not ui mode night: %s\n", part.string());
+    }
+
+    // density
+    if (getDensityName(part.string())) {
+        den = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not density: %s\n", part.string());
+    }
+
+    // touchscreen
+    if (getTouchscreenName(part.string())) {
+        touch = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not touchscreen: %s\n", part.string());
+    }
+
+    // keyboard hidden
+    if (getKeysHiddenName(part.string())) {
+        keysHidden = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not keysHidden: %s\n", part.string());
+    }
+
+    // keyboard
+    if (getKeyboardName(part.string())) {
+        key = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not keyboard: %s\n", part.string());
+    }
+
+    // navigation hidden
+    if (getNavHiddenName(part.string())) {
+        navHidden = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not navHidden: %s\n", part.string());
+    }
+
+    if (getNavigationName(part.string())) {
+        nav = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not navigation: %s\n", part.string());
+    }
+
+    if (getScreenSizeName(part.string())) {
+        size = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not screen size: %s\n", part.string());
+    }
+
+    if (getVersionName(part.string())) {
+        vers = part;
+
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index];
+    } else {
+        //printf("not version: %s\n", part.string());
+    }
+
+    // if there are extra parts, it doesn't match
+    return false;
+
+success:
+    this->mcc = mcc;
+    this->mnc = mnc;
+    this->locale = loc;
+    this->screenLayoutSize = layoutsize;
+    this->screenLayoutLong = layoutlong;
+    this->smallestScreenWidthDp = smallestwidthdp;
+    this->screenWidthDp = widthdp;
+    this->screenHeightDp = heightdp;
+    this->orientation = orient;
+    this->uiModeType = uiModeType;
+    this->uiModeNight = uiModeNight;
+    this->density = den;
+    this->touchscreen = touch;
+    this->keysHidden = keysHidden;
+    this->keyboard = key;
+    this->navHidden = navHidden;
+    this->navigation = nav;
+    this->screenSize = size;
+    this->layoutDirection = layoutDir;
+    this->version = vers;
+
+    // what is this anyway?
+    this->vendor = "";
+
+    return true;
+}
+
+String8
+AaptGroupEntry::toString() const
+{
+    String8 s = this->mcc;
+    s += ",";
+    s += this->mnc;
+    s += ",";
+    s += this->locale;
+    s += ",";
+    s += layoutDirection;
+    s += ",";
+    s += smallestScreenWidthDp;
+    s += ",";
+    s += screenWidthDp;
+    s += ",";
+    s += screenHeightDp;
+    s += ",";
+    s += screenLayoutSize;
+    s += ",";
+    s += screenLayoutLong;
+    s += ",";
+    s += this->orientation;
+    s += ",";
+    s += uiModeType;
+    s += ",";
+    s += uiModeNight;
+    s += ",";
+    s += density;
+    s += ",";
+    s += touchscreen;
+    s += ",";
+    s += keysHidden;
+    s += ",";
+    s += keyboard;
+    s += ",";
+    s += navHidden;
+    s += ",";
+    s += navigation;
+    s += ",";
+    s += screenSize;
+    s += ",";
+    s += version;
+    return s;
+}
+
+String8
+AaptGroupEntry::toDirName(const String8& resType) const
+{
+    String8 s = resType;
+    if (this->mcc != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += mcc;
+    }
+    if (this->mnc != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += mnc;
+    }
+    if (this->locale != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += locale;
+    }
+    if (this->layoutDirection != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += layoutDirection;
+    }
+    if (this->smallestScreenWidthDp != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += smallestScreenWidthDp;
+    }
+    if (this->screenWidthDp != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += screenWidthDp;
+    }
+    if (this->screenHeightDp != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += screenHeightDp;
+    }
+    if (this->screenLayoutSize != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += screenLayoutSize;
+    }
+    if (this->screenLayoutLong != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += screenLayoutLong;
+    }
+    if (this->orientation != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += orientation;
+    }
+    if (this->uiModeType != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += uiModeType;
+    }
+    if (this->uiModeNight != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += uiModeNight;
+    }
+    if (this->density != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += density;
+    }
+    if (this->touchscreen != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += touchscreen;
+    }
+    if (this->keysHidden != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += keysHidden;
+    }
+    if (this->keyboard != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += keyboard;
+    }
+    if (this->navHidden != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += navHidden;
+    }
+    if (this->navigation != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += navigation;
+    }
+    if (this->screenSize != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += screenSize;
+    }
+    if (this->version != "") {
+        if (s.length() > 0) {
+            s += "-";
+        }
+        s += version;
+    }
+
+    return s;
+}
+
+bool AaptGroupEntry::getMccName(const char* name,
+                                    ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->mcc = 0;
+        return true;
+    }
+    const char* c = name;
+    if (tolower(*c) != 'm') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+
+    const char* val = c;
+
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+    if (*c != 0) return false;
+    if (c-val != 3) return false;
+
+    int d = atoi(val);
+    if (d != 0) {
+        if (out) out->mcc = d;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getMncName(const char* name,
+                                    ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->mcc = 0;
+        return true;
+    }
+    const char* c = name;
+    if (tolower(*c) != 'm') return false;
+    c++;
+    if (tolower(*c) != 'n') return false;
+    c++;
+    if (tolower(*c) != 'c') return false;
+    c++;
+
+    const char* val = c;
+
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+    if (*c != 0) return false;
+    if (c-val == 0 || c-val > 3) return false;
+
+    if (out) {
+        out->mnc = atoi(val);
+        if (out->mnc == 0) {
+            out->mnc = ACONFIGURATION_MNC_ZERO;
+        }
+    }
+
+    return true;
+}
+
+/*
+ * Does this directory name fit the pattern of a locale dir ("en-rUS" or
+ * "default")?
+ *
+ * TODO: Should insist that the first two letters are lower case, and the
+ * second two are upper.
+ */
+bool AaptGroupEntry::getLocaleName(const char* fileName,
+                                   ResTable_config* out)
+{
+    if (strcmp(fileName, kWildcardName) == 0
+            || strcmp(fileName, kDefaultLocale) == 0) {
+        if (out) {
+            out->language[0] = 0;
+            out->language[1] = 0;
+            out->country[0] = 0;
+            out->country[1] = 0;
+        }
+        return true;
+    }
+
+    if (strlen(fileName) == 2 && isalpha(fileName[0]) && isalpha(fileName[1])) {
+        if (out) {
+            out->language[0] = fileName[0];
+            out->language[1] = fileName[1];
+            out->country[0] = 0;
+            out->country[1] = 0;
+        }
+        return true;
+    }
+
+    if (strlen(fileName) == 5 &&
+        isalpha(fileName[0]) &&
+        isalpha(fileName[1]) &&
+        fileName[2] == '-' &&
+        isalpha(fileName[3]) &&
+        isalpha(fileName[4])) {
+        if (out) {
+            out->language[0] = fileName[0];
+            out->language[1] = fileName[1];
+            out->country[0] = fileName[3];
+            out->country[1] = fileName[4];
+        }
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getLayoutDirectionName(const char* name, ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_ANY;
+        return true;
+    } else if (strcmp(name, "ldltr") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_LTR;
+        return true;
+    } else if (strcmp(name, "ldrtl") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+                | ResTable_config::LAYOUTDIR_RTL;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getScreenLayoutSizeName(const char* name,
+                                     ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_ANY;
+        return true;
+    } else if (strcmp(name, "small") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_SMALL;
+        return true;
+    } else if (strcmp(name, "normal") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_NORMAL;
+        return true;
+    } else if (strcmp(name, "large") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_LARGE;
+        return true;
+    } else if (strcmp(name, "xlarge") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+                | ResTable_config::SCREENSIZE_XLARGE;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getScreenLayoutLongName(const char* name,
+                                     ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_ANY;
+        return true;
+    } else if (strcmp(name, "long") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_YES;
+        return true;
+    } else if (strcmp(name, "notlong") == 0) {
+        if (out) out->screenLayout =
+                (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+                | ResTable_config::SCREENLONG_NO;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getOrientationName(const char* name,
+                                        ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->orientation = out->ORIENTATION_ANY;
+        return true;
+    } else if (strcmp(name, "port") == 0) {
+        if (out) out->orientation = out->ORIENTATION_PORT;
+        return true;
+    } else if (strcmp(name, "land") == 0) {
+        if (out) out->orientation = out->ORIENTATION_LAND;
+        return true;
+    } else if (strcmp(name, "square") == 0) {
+        if (out) out->orientation = out->ORIENTATION_SQUARE;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getUiModeTypeName(const char* name,
+                                       ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+                | ResTable_config::UI_MODE_TYPE_ANY;
+        return true;
+    } else if (strcmp(name, "desk") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_DESK;
+        return true;
+    } else if (strcmp(name, "car") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_CAR;
+        return true;
+    } else if (strcmp(name, "television") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_TELEVISION;
+        return true;
+    } else if (strcmp(name, "appliance") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_APPLIANCE;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getUiModeNightName(const char* name,
+                                          ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+                | ResTable_config::UI_MODE_NIGHT_ANY;
+        return true;
+    } else if (strcmp(name, "night") == 0) {
+        if (out) out->uiMode =
+                (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+                | ResTable_config::UI_MODE_NIGHT_YES;
+        return true;
+    } else if (strcmp(name, "notnight") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+              | ResTable_config::UI_MODE_NIGHT_NO;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getDensityName(const char* name,
+                                    ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->density = ResTable_config::DENSITY_DEFAULT;
+        return true;
+    }
+    
+    if (strcmp(name, "nodpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_NONE;
+        return true;
+    }
+    
+    if (strcmp(name, "ldpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_LOW;
+        return true;
+    }
+    
+    if (strcmp(name, "mdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_MEDIUM;
+        return true;
+    }
+    
+    if (strcmp(name, "tvdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_TV;
+        return true;
+    }
+    
+    if (strcmp(name, "hdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_HIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XHIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xxhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+        return true;
+    }
+
+    if (strcmp(name, "xxxhdpi") == 0) {
+        if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+        return true;
+    }
+
+    char* c = (char*)name;
+    while (*c >= '0' && *c <= '9') {
+        c++;
+    }
+
+    // check that we have 'dpi' after the last digit.
+    if (toupper(c[0]) != 'D' ||
+            toupper(c[1]) != 'P' ||
+            toupper(c[2]) != 'I' ||
+            c[3] != 0) {
+        return false;
+    }
+
+    // temporarily replace the first letter with \0 to
+    // use atoi.
+    char tmp = c[0];
+    c[0] = '\0';
+
+    int d = atoi(name);
+    c[0] = tmp;
+
+    if (d != 0) {
+        if (out) out->density = d;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getTouchscreenName(const char* name,
+                                        ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+        return true;
+    } else if (strcmp(name, "notouch") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+        return true;
+    } else if (strcmp(name, "stylus") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+        return true;
+    } else if (strcmp(name, "finger") == 0) {
+        if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getKeysHiddenName(const char* name,
+                                       ResTable_config* out)
+{
+    uint8_t mask = 0;
+    uint8_t value = 0;
+    if (strcmp(name, kWildcardName) == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_ANY;
+    } else if (strcmp(name, "keysexposed") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_NO;
+    } else if (strcmp(name, "keyshidden") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_YES;
+    } else if (strcmp(name, "keyssoft") == 0) {
+        mask = ResTable_config::MASK_KEYSHIDDEN;
+        value = ResTable_config::KEYSHIDDEN_SOFT;
+    }
+
+    if (mask != 0) {
+        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getKeyboardName(const char* name,
+                                        ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->keyboard = out->KEYBOARD_ANY;
+        return true;
+    } else if (strcmp(name, "nokeys") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+        return true;
+    } else if (strcmp(name, "qwerty") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_QWERTY;
+        return true;
+    } else if (strcmp(name, "12key") == 0) {
+        if (out) out->keyboard = out->KEYBOARD_12KEY;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getNavHiddenName(const char* name,
+                                       ResTable_config* out)
+{
+    uint8_t mask = 0;
+    uint8_t value = 0;
+    if (strcmp(name, kWildcardName) == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_ANY;
+    } else if (strcmp(name, "navexposed") == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_NO;
+    } else if (strcmp(name, "navhidden") == 0) {
+        mask = ResTable_config::MASK_NAVHIDDEN;
+        value = ResTable_config::NAVHIDDEN_YES;
+    }
+
+    if (mask != 0) {
+        if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getNavigationName(const char* name,
+                                     ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) out->navigation = out->NAVIGATION_ANY;
+        return true;
+    } else if (strcmp(name, "nonav") == 0) {
+        if (out) out->navigation = out->NAVIGATION_NONAV;
+        return true;
+    } else if (strcmp(name, "dpad") == 0) {
+        if (out) out->navigation = out->NAVIGATION_DPAD;
+        return true;
+    } else if (strcmp(name, "trackball") == 0) {
+        if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+        return true;
+    } else if (strcmp(name, "wheel") == 0) {
+        if (out) out->navigation = out->NAVIGATION_WHEEL;
+        return true;
+    }
+
+    return false;
+}
+
+bool AaptGroupEntry::getScreenSizeName(const char* name, ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenWidth = out->SCREENWIDTH_ANY;
+            out->screenHeight = out->SCREENHEIGHT_ANY;
+        }
+        return true;
+    }
+
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || *x != 'x') return false;
+    String8 xName(name, x-name);
+    x++;
+
+    const char* y = x;
+    while (*y >= '0' && *y <= '9') y++;
+    if (y == name || *y != 0) return false;
+    String8 yName(x, y-x);
+
+    uint16_t w = (uint16_t)atoi(xName.string());
+    uint16_t h = (uint16_t)atoi(yName.string());
+    if (w < h) {
+        return false;
+    }
+
+    if (out) {
+        out->screenWidth = w;
+        out->screenHeight = h;
+    }
+
+    return true;
+}
+
+bool AaptGroupEntry::getSmallestScreenWidthDpName(const char* name, ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 's') return false;
+    name++;
+    if (*name != 'w') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    String8 xName(name, x-name);
+
+    if (out) {
+        out->smallestScreenWidthDp = (uint16_t)atoi(xName.string());
+    }
+
+    return true;
+}
+
+bool AaptGroupEntry::getScreenWidthDpName(const char* name, ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenWidthDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'w') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    String8 xName(name, x-name);
+
+    if (out) {
+        out->screenWidthDp = (uint16_t)atoi(xName.string());
+    }
+
+    return true;
+}
+
+bool AaptGroupEntry::getScreenHeightDpName(const char* name, ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->screenHeightDp = out->SCREENWIDTH_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'h') return false;
+    name++;
+    const char* x = name;
+    while (*x >= '0' && *x <= '9') x++;
+    if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+    String8 xName(name, x-name);
+
+    if (out) {
+        out->screenHeightDp = (uint16_t)atoi(xName.string());
+    }
+
+    return true;
+}
+
+bool AaptGroupEntry::getVersionName(const char* name, ResTable_config* out)
+{
+    if (strcmp(name, kWildcardName) == 0) {
+        if (out) {
+            out->sdkVersion = out->SDKVERSION_ANY;
+            out->minorVersion = out->MINORVERSION_ANY;
+        }
+        return true;
+    }
+
+    if (*name != 'v') {
+        return false;
+    }
+
+    name++;
+    const char* s = name;
+    while (*s >= '0' && *s <= '9') s++;
+    if (s == name || *s != 0) return false;
+    String8 sdkName(name, s-name);
+
+    if (out) {
+        out->sdkVersion = (uint16_t)atoi(sdkName.string());
+        out->minorVersion = 0;
+    }
+
+    return true;
+}
+
+int AaptGroupEntry::compare(const AaptGroupEntry& o) const
+{
+    int v = mcc.compare(o.mcc);
+    if (v == 0) v = mnc.compare(o.mnc);
+    if (v == 0) v = locale.compare(o.locale);
+    if (v == 0) v = layoutDirection.compare(o.layoutDirection);
+    if (v == 0) v = vendor.compare(o.vendor);
+    if (v == 0) v = smallestScreenWidthDp.compare(o.smallestScreenWidthDp);
+    if (v == 0) v = screenWidthDp.compare(o.screenWidthDp);
+    if (v == 0) v = screenHeightDp.compare(o.screenHeightDp);
+    if (v == 0) v = screenLayoutSize.compare(o.screenLayoutSize);
+    if (v == 0) v = screenLayoutLong.compare(o.screenLayoutLong);
+    if (v == 0) v = orientation.compare(o.orientation);
+    if (v == 0) v = uiModeType.compare(o.uiModeType);
+    if (v == 0) v = uiModeNight.compare(o.uiModeNight);
+    if (v == 0) v = density.compare(o.density);
+    if (v == 0) v = touchscreen.compare(o.touchscreen);
+    if (v == 0) v = keysHidden.compare(o.keysHidden);
+    if (v == 0) v = keyboard.compare(o.keyboard);
+    if (v == 0) v = navHidden.compare(o.navHidden);
+    if (v == 0) v = navigation.compare(o.navigation);
+    if (v == 0) v = screenSize.compare(o.screenSize);
+    if (v == 0) v = version.compare(o.version);
+    return v;
+}
+
+const ResTable_config& AaptGroupEntry::toParams() const
+{
+    if (!mParamsChanged) {
+        return mParams;
+    }
+
+    mParamsChanged = false;
+    ResTable_config& params(mParams);
+    memset(&params, 0, sizeof(params));
+    getMccName(mcc.string(), &params);
+    getMncName(mnc.string(), &params);
+    getLocaleName(locale.string(), &params);
+    getLayoutDirectionName(layoutDirection.string(), &params);
+    getSmallestScreenWidthDpName(smallestScreenWidthDp.string(), &params);
+    getScreenWidthDpName(screenWidthDp.string(), &params);
+    getScreenHeightDpName(screenHeightDp.string(), &params);
+    getScreenLayoutSizeName(screenLayoutSize.string(), &params);
+    getScreenLayoutLongName(screenLayoutLong.string(), &params);
+    getOrientationName(orientation.string(), &params);
+    getUiModeTypeName(uiModeType.string(), &params);
+    getUiModeNightName(uiModeNight.string(), &params);
+    getDensityName(density.string(), &params);
+    getTouchscreenName(touchscreen.string(), &params);
+    getKeysHiddenName(keysHidden.string(), &params);
+    getKeyboardName(keyboard.string(), &params);
+    getNavHiddenName(navHidden.string(), &params);
+    getNavigationName(navigation.string(), &params);
+    getScreenSizeName(screenSize.string(), &params);
+    getVersionName(version.string(), &params);
+    
+    // Fix up version number based on specified parameters.
+    int minSdk = 0;
+    if (params.smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || params.screenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || params.screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+        minSdk = SDK_HONEYCOMB_MR2;
+    } else if ((params.uiMode&ResTable_config::MASK_UI_MODE_TYPE)
+                != ResTable_config::UI_MODE_TYPE_ANY
+            ||  (params.uiMode&ResTable_config::MASK_UI_MODE_NIGHT)
+                != ResTable_config::UI_MODE_NIGHT_ANY) {
+        minSdk = SDK_FROYO;
+    } else if ((params.screenLayout&ResTable_config::MASK_SCREENSIZE)
+                != ResTable_config::SCREENSIZE_ANY
+            ||  (params.screenLayout&ResTable_config::MASK_SCREENLONG)
+                != ResTable_config::SCREENLONG_ANY
+            || params.density != ResTable_config::DENSITY_DEFAULT) {
+        minSdk = SDK_DONUT;
+    }
+    
+    if (minSdk > params.sdkVersion) {
+        params.sdkVersion = minSdk;
+    }
+    
+    return params;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+void* AaptFile::editData(size_t size)
+{
+    if (size <= mBufferSize) {
+        mDataSize = size;
+        return mData;
+    }
+    size_t allocSize = (size*3)/2;
+    void* buf = realloc(mData, allocSize);
+    if (buf == NULL) {
+        return NULL;
+    }
+    mData = buf;
+    mDataSize = size;
+    mBufferSize = allocSize;
+    return buf;
+}
+
+void* AaptFile::editData(size_t* outSize)
+{
+    if (outSize) {
+        *outSize = mDataSize;
+    }
+    return mData;
+}
+
+void* AaptFile::padData(size_t wordSize)
+{
+    const size_t extra = mDataSize%wordSize;
+    if (extra == 0) {
+        return mData;
+    }
+
+    size_t initial = mDataSize;
+    void* data = editData(initial+(wordSize-extra));
+    if (data != NULL) {
+        memset(((uint8_t*)data) + initial, 0, wordSize-extra);
+    }
+    return data;
+}
+
+status_t AaptFile::writeData(const void* data, size_t size)
+{
+    size_t end = mDataSize;
+    size_t total = size + end;
+    void* buf = editData(total);
+    if (buf == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    memcpy(((char*)buf)+end, data, size);
+    return NO_ERROR;
+}
+
+void AaptFile::clearData()
+{
+    if (mData != NULL) free(mData);
+    mData = NULL;
+    mDataSize = 0;
+    mBufferSize = 0;
+}
+
+String8 AaptFile::getPrintableSource() const
+{
+    if (hasData()) {
+        String8 name(mGroupEntry.toDirName(String8()));
+        name.appendPath(mPath);
+        name.append(" #generated");
+        return name;
+    }
+    return mSourceFile;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptGroup::addFile(const sp<AaptFile>& file)
+{
+    if (mFiles.indexOfKey(file->getGroupEntry()) < 0) {
+        file->mPath = mPath;
+        mFiles.add(file->getGroupEntry(), file);
+        return NO_ERROR;
+    }
+
+#if 0
+    printf("Error adding file %s: group %s already exists in leaf=%s path=%s\n",
+            file->getSourceFile().string(),
+            file->getGroupEntry().toDirName(String8()).string(),
+            mLeaf.string(), mPath.string());
+#endif
+
+    SourcePos(file->getSourceFile(), -1).error("Duplicate file.\n%s: Original is here.",
+                                               getPrintableSource().string());
+    return UNKNOWN_ERROR;
+}
+
+void AaptGroup::removeFile(size_t index)
+{
+	mFiles.removeItemsAt(index);
+}
+
+void AaptGroup::print(const String8& prefix) const
+{
+    printf("%s%s\n", prefix.string(), getPath().string());
+    const size_t N=mFiles.size();
+    size_t i;
+    for (i=0; i<N; i++) {
+        sp<AaptFile> file = mFiles.valueAt(i);
+        const AaptGroupEntry& e = file->getGroupEntry();
+        if (file->hasData()) {
+            printf("%s  Gen: (%s) %d bytes\n", prefix.string(), e.toDirName(String8()).string(),
+                    (int)file->getSize());
+        } else {
+            printf("%s  Src: (%s) %s\n", prefix.string(), e.toDirName(String8()).string(),
+                    file->getPrintableSource().string());
+        }
+        //printf("%s  File Group Entry: %s\n", prefix.string(),
+        //        file->getGroupEntry().toDirName(String8()).string());
+    }
+}
+
+String8 AaptGroup::getPrintableSource() const
+{
+    if (mFiles.size() > 0) {
+        // Arbitrarily pull the first source file out of the list.
+        return mFiles.valueAt(0)->getPrintableSource();
+    }
+
+    // Should never hit this case, but to be safe...
+    return getPath();
+
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptDir::addFile(const String8& name, const sp<AaptGroup>& file)
+{
+    if (mFiles.indexOfKey(name) >= 0) {
+        return ALREADY_EXISTS;
+    }
+    mFiles.add(name, file);
+    return NO_ERROR;
+}
+
+status_t AaptDir::addDir(const String8& name, const sp<AaptDir>& dir)
+{
+    if (mDirs.indexOfKey(name) >= 0) {
+        return ALREADY_EXISTS;
+    }
+    mDirs.add(name, dir);
+    return NO_ERROR;
+}
+
+sp<AaptDir> AaptDir::makeDir(const String8& path)
+{
+    String8 name;
+    String8 remain = path;
+
+    sp<AaptDir> subdir = this;
+    while (name = remain.walkPath(&remain), remain != "") {
+        subdir = subdir->makeDir(name);
+    }
+
+    ssize_t i = subdir->mDirs.indexOfKey(name);
+    if (i >= 0) {
+        return subdir->mDirs.valueAt(i);
+    }
+    sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
+    subdir->mDirs.add(name, dir);
+    return dir;
+}
+
+void AaptDir::removeFile(const String8& name)
+{
+    mFiles.removeItem(name);
+}
+
+void AaptDir::removeDir(const String8& name)
+{
+    mDirs.removeItem(name);
+}
+
+status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file)
+{
+    sp<AaptGroup> group;
+    if (mFiles.indexOfKey(leafName) >= 0) {
+        group = mFiles.valueFor(leafName);
+    } else {
+        group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
+        mFiles.add(leafName, group);
+    }
+
+    return group->addFile(file);
+}
+
+ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
+                            const AaptGroupEntry& kind, const String8& resType,
+                            sp<FilePathStore>& fullResPaths)
+{
+    Vector<String8> fileNames;
+    {
+        DIR* dir = NULL;
+
+        dir = opendir(srcDir.string());
+        if (dir == NULL) {
+            fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
+            return UNKNOWN_ERROR;
+        }
+
+        /*
+         * Slurp the filenames out of the directory.
+         */
+        while (1) {
+            struct dirent* entry;
+
+            entry = readdir(dir);
+            if (entry == NULL)
+                break;
+
+            if (isHidden(srcDir.string(), entry->d_name))
+                continue;
+
+            String8 name(entry->d_name);
+            fileNames.add(name);
+            // Add fully qualified path for dependency purposes
+            // if we're collecting them
+            if (fullResPaths != NULL) {
+                fullResPaths->add(srcDir.appendPathCopy(name));
+            }
+        }
+        closedir(dir);
+    }
+
+    ssize_t count = 0;
+
+    /*
+     * Stash away the files and recursively descend into subdirectories.
+     */
+    const size_t N = fileNames.size();
+    size_t i;
+    for (i = 0; i < N; i++) {
+        String8 pathName(srcDir);
+        FileType type;
+
+        pathName.appendPath(fileNames[i].string());
+        type = getFileType(pathName.string());
+        if (type == kFileTypeDirectory) {
+            sp<AaptDir> subdir;
+            bool notAdded = false;
+            if (mDirs.indexOfKey(fileNames[i]) >= 0) {
+                subdir = mDirs.valueFor(fileNames[i]);
+            } else {
+                subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
+                notAdded = true;
+            }
+            ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
+                                                resType, fullResPaths);
+            if (res < NO_ERROR) {
+                return res;
+            }
+            if (res > 0 && notAdded) {
+                mDirs.add(fileNames[i], subdir);
+            }
+            count += res;
+        } else if (type == kFileTypeRegular) {
+            sp<AaptFile> file = new AaptFile(pathName, kind, resType);
+            status_t err = addLeafFile(fileNames[i], file);
+            if (err != NO_ERROR) {
+                return err;
+            }
+
+            count++;
+
+        } else {
+            if (bundle->getVerbose())
+                printf("   (ignoring non-file/dir '%s')\n", pathName.string());
+        }
+    }
+
+    return count;
+}
+
+status_t AaptDir::validate() const
+{
+    const size_t NF = mFiles.size();
+    const size_t ND = mDirs.size();
+    size_t i;
+    for (i = 0; i < NF; i++) {
+        if (!validateFileName(mFiles.valueAt(i)->getLeaf().string())) {
+            SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+                    "Invalid filename.  Unable to add.");
+            return UNKNOWN_ERROR;
+        }
+
+        size_t j;
+        for (j = i+1; j < NF; j++) {
+            if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
+                           mFiles.valueAt(j)->getLeaf().string()) == 0) {
+                SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+                        "File is case-insensitive equivalent to: %s",
+                        mFiles.valueAt(j)->getPrintableSource().string());
+                return UNKNOWN_ERROR;
+            }
+
+            // TODO: if ".gz", check for non-.gz; if non-, check for ".gz"
+            // (this is mostly caught by the "marked" stuff, below)
+        }
+
+        for (j = 0; j < ND; j++) {
+            if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
+                           mDirs.valueAt(j)->getLeaf().string()) == 0) {
+                SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+                        "File conflicts with dir from: %s",
+                        mDirs.valueAt(j)->getPrintableSource().string());
+                return UNKNOWN_ERROR;
+            }
+        }
+    }
+
+    for (i = 0; i < ND; i++) {
+        if (!validateFileName(mDirs.valueAt(i)->getLeaf().string())) {
+            SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
+                    "Invalid directory name, unable to add.");
+            return UNKNOWN_ERROR;
+        }
+
+        size_t j;
+        for (j = i+1; j < ND; j++) {
+            if (strcasecmp(mDirs.valueAt(i)->getLeaf().string(),
+                           mDirs.valueAt(j)->getLeaf().string()) == 0) {
+                SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
+                        "Directory is case-insensitive equivalent to: %s",
+                        mDirs.valueAt(j)->getPrintableSource().string());
+                return UNKNOWN_ERROR;
+            }
+        }
+
+        status_t err = mDirs.valueAt(i)->validate();
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+void AaptDir::print(const String8& prefix) const
+{
+    const size_t ND=getDirs().size();
+    size_t i;
+    for (i=0; i<ND; i++) {
+        getDirs().valueAt(i)->print(prefix);
+    }
+
+    const size_t NF=getFiles().size();
+    for (i=0; i<NF; i++) {
+        getFiles().valueAt(i)->print(prefix);
+    }
+}
+
+String8 AaptDir::getPrintableSource() const
+{
+    if (mFiles.size() > 0) {
+        // Arbitrarily pull the first file out of the list as the source dir.
+        return mFiles.valueAt(0)->getPrintableSource().getPathDir();
+    }
+    if (mDirs.size() > 0) {
+        // Or arbitrarily pull the first dir out of the list as the source dir.
+        return mDirs.valueAt(0)->getPrintableSource().getPathDir();
+    }
+
+    // Should never hit this case, but to be safe...
+    return mPath;
+
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptSymbols::applyJavaSymbols(const sp<AaptSymbols>& javaSymbols)
+{
+    status_t err = NO_ERROR;
+    size_t N = javaSymbols->mSymbols.size();
+    for (size_t i=0; i<N; i++) {
+        const String8& name = javaSymbols->mSymbols.keyAt(i);
+        const AaptSymbolEntry& entry = javaSymbols->mSymbols.valueAt(i);
+        ssize_t pos = mSymbols.indexOfKey(name);
+        if (pos < 0) {
+            entry.sourcePos.error("Symbol '%s' declared with <java-symbol> not defined\n", name.string());
+            err = UNKNOWN_ERROR;
+            continue;
+        }
+        //printf("**** setting symbol #%d/%d %s to isJavaSymbol=%d\n",
+        //        i, N, name.string(), entry.isJavaSymbol ? 1 : 0);
+        mSymbols.editValueAt(pos).isJavaSymbol = entry.isJavaSymbol;
+    }
+
+    N = javaSymbols->mNestedSymbols.size();
+    for (size_t i=0; i<N; i++) {
+        const String8& name = javaSymbols->mNestedSymbols.keyAt(i);
+        const sp<AaptSymbols>& symbols = javaSymbols->mNestedSymbols.valueAt(i);
+        ssize_t pos = mNestedSymbols.indexOfKey(name);
+        if (pos < 0) {
+            SourcePos pos;
+            pos.error("Java symbol dir %s not defined\n", name.string());
+            err = UNKNOWN_ERROR;
+            continue;
+        }
+        //printf("**** applying java symbols in dir %s\n", name.string());
+        status_t myerr = mNestedSymbols.valueAt(pos)->applyJavaSymbols(symbols);
+        if (myerr != NO_ERROR) {
+            err = myerr;
+        }
+    }
+
+    return err;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+AaptAssets::AaptAssets()
+    : AaptDir(String8(), String8()),
+      mChanged(false), mHaveIncludedAssets(false), mRes(NULL)
+{
+}
+
+const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const {
+    if (mChanged) {
+    }
+    return mGroupEntries;
+}
+
+status_t AaptAssets::addFile(const String8& name, const sp<AaptGroup>& file)
+{
+    mChanged = true;
+    return AaptDir::addFile(name, file);
+}
+
+sp<AaptFile> AaptAssets::addFile(
+        const String8& filePath, const AaptGroupEntry& entry,
+        const String8& srcDir, sp<AaptGroup>* outGroup,
+        const String8& resType)
+{
+    sp<AaptDir> dir = this;
+    sp<AaptGroup> group;
+    sp<AaptFile> file;
+    String8 root, remain(filePath), partialPath;
+    while (remain.length() > 0) {
+        root = remain.walkPath(&remain);
+        partialPath.appendPath(root);
+
+        const String8 rootStr(root);
+
+        if (remain.length() == 0) {
+            ssize_t i = dir->getFiles().indexOfKey(rootStr);
+            if (i >= 0) {
+                group = dir->getFiles().valueAt(i);
+            } else {
+                group = new AaptGroup(rootStr, filePath);
+                status_t res = dir->addFile(rootStr, group);
+                if (res != NO_ERROR) {
+                    return NULL;
+                }
+            }
+            file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
+            status_t res = group->addFile(file);
+            if (res != NO_ERROR) {
+                return NULL;
+            }
+            break;
+
+        } else {
+            ssize_t i = dir->getDirs().indexOfKey(rootStr);
+            if (i >= 0) {
+                dir = dir->getDirs().valueAt(i);
+            } else {
+                sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
+                status_t res = dir->addDir(rootStr, subdir);
+                if (res != NO_ERROR) {
+                    return NULL;
+                }
+                dir = subdir;
+            }
+        }
+    }
+
+    mGroupEntries.add(entry);
+    if (outGroup) *outGroup = group;
+    return file;
+}
+
+void AaptAssets::addResource(const String8& leafName, const String8& path,
+                const sp<AaptFile>& file, const String8& resType)
+{
+    sp<AaptDir> res = AaptDir::makeDir(kResString);
+    String8 dirname = file->getGroupEntry().toDirName(resType);
+    sp<AaptDir> subdir = res->makeDir(dirname);
+    sp<AaptGroup> grr = new AaptGroup(leafName, path);
+    grr->addFile(file);
+
+    subdir->addFile(leafName, grr);
+}
+
+
+ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
+{
+    int count;
+    int totalCount = 0;
+    FileType type;
+    const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
+    const size_t dirCount =resDirs.size();
+    sp<AaptAssets> current = this;
+
+    const int N = bundle->getFileSpecCount();
+
+    /*
+     * If a package manifest was specified, include that first.
+     */
+    if (bundle->getAndroidManifestFile() != NULL) {
+        // place at root of zip.
+        String8 srcFile(bundle->getAndroidManifestFile());
+        addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
+                NULL, String8());
+        totalCount++;
+    }
+
+    /*
+     * If a directory of custom assets was supplied, slurp 'em up.
+     */
+    if (bundle->getAssetSourceDir()) {
+        const char* assetDir = bundle->getAssetSourceDir();
+
+        FileType type = getFileType(assetDir);
+        if (type == kFileTypeNonexistent) {
+            fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDir);
+            return UNKNOWN_ERROR;
+        }
+        if (type != kFileTypeDirectory) {
+            fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
+            return UNKNOWN_ERROR;
+        }
+
+        String8 assetRoot(assetDir);
+        sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
+        AaptGroupEntry group;
+        count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
+                                            String8(), mFullAssetPaths);
+        if (count < 0) {
+            totalCount = count;
+            goto bail;
+        }
+        if (count > 0) {
+            mGroupEntries.add(group);
+        }
+        totalCount += count;
+
+        if (bundle->getVerbose())
+            printf("Found %d custom asset file%s in %s\n",
+                   count, (count==1) ? "" : "s", assetDir);
+    }
+
+    /*
+     * If a directory of resource-specific assets was supplied, slurp 'em up.
+     */
+    for (size_t i=0; i<dirCount; i++) {
+        const char *res = resDirs[i];
+        if (res) {
+            type = getFileType(res);
+            if (type == kFileTypeNonexistent) {
+                fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
+                return UNKNOWN_ERROR;
+            }
+            if (type == kFileTypeDirectory) {
+                if (i>0) {
+                    sp<AaptAssets> nextOverlay = new AaptAssets();
+                    current->setOverlay(nextOverlay);
+                    current = nextOverlay;
+                    current->setFullResPaths(mFullResPaths);
+                }
+                count = current->slurpResourceTree(bundle, String8(res));
+
+                if (count < 0) {
+                    totalCount = count;
+                    goto bail;
+                }
+                totalCount += count;
+            }
+            else {
+                fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
+                return UNKNOWN_ERROR;
+            }
+        }
+        
+    }
+    /*
+     * Now do any additional raw files.
+     */
+    for (int arg=0; arg<N; arg++) {
+        const char* assetDir = bundle->getFileSpecEntry(arg);
+
+        FileType type = getFileType(assetDir);
+        if (type == kFileTypeNonexistent) {
+            fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
+            return UNKNOWN_ERROR;
+        }
+        if (type != kFileTypeDirectory) {
+            fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
+            return UNKNOWN_ERROR;
+        }
+
+        String8 assetRoot(assetDir);
+
+        if (bundle->getVerbose())
+            printf("Processing raw dir '%s'\n", (const char*) assetDir);
+
+        /*
+         * Do a recursive traversal of subdir tree.  We don't make any
+         * guarantees about ordering, so we're okay with an inorder search
+         * using whatever order the OS happens to hand back to us.
+         */
+        count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths);
+        if (count < 0) {
+            /* failure; report error and remove archive */
+            totalCount = count;
+            goto bail;
+        }
+        totalCount += count;
+
+        if (bundle->getVerbose())
+            printf("Found %d asset file%s in %s\n",
+                   count, (count==1) ? "" : "s", assetDir);
+    }
+
+    count = validate();
+    if (count != NO_ERROR) {
+        totalCount = count;
+        goto bail;
+    }
+
+    count = filter(bundle);
+    if (count != NO_ERROR) {
+        totalCount = count;
+        goto bail;
+    }
+
+bail:
+    return totalCount;
+}
+
+ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
+                                    const AaptGroupEntry& kind,
+                                    const String8& resType,
+                                    sp<FilePathStore>& fullResPaths)
+{
+    ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths);
+    if (res > 0) {
+        mGroupEntries.add(kind);
+    }
+
+    return res;
+}
+
+ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
+{
+    ssize_t err = 0;
+
+    DIR* dir = opendir(srcDir.string());
+    if (dir == NULL) {
+        fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    status_t count = 0;
+
+    /*
+     * Run through the directory, looking for dirs that match the
+     * expected pattern.
+     */
+    while (1) {
+        struct dirent* entry = readdir(dir);
+        if (entry == NULL) {
+            break;
+        }
+
+        if (isHidden(srcDir.string(), entry->d_name)) {
+            continue;
+        }
+
+        String8 subdirName(srcDir);
+        subdirName.appendPath(entry->d_name);
+
+        AaptGroupEntry group;
+        String8 resType;
+        bool b = group.initFromDirName(entry->d_name, &resType);
+        if (!b) {
+            fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(),
+                    entry->d_name);
+            err = -1;
+            continue;
+        }
+
+        if (bundle->getMaxResVersion() != NULL && group.getVersionString().length() != 0) {
+            int maxResInt = atoi(bundle->getMaxResVersion());
+            const char *verString = group.getVersionString().string();
+            int dirVersionInt = atoi(verString + 1); // skip 'v' in version name
+            if (dirVersionInt > maxResInt) {
+              fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name);
+              continue;
+            }
+        }
+
+        FileType type = getFileType(subdirName.string());
+
+        if (type == kFileTypeDirectory) {
+            sp<AaptDir> dir = makeDir(resType);
+            ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
+                                                resType, mFullResPaths);
+            if (res < 0) {
+                count = res;
+                goto bail;
+            }
+            if (res > 0) {
+                mGroupEntries.add(group);
+                count += res;
+            }
+
+            // Only add this directory if we don't already have a resource dir
+            // for the current type.  This ensures that we only add the dir once
+            // for all configs.
+            sp<AaptDir> rdir = resDir(resType);
+            if (rdir == NULL) {
+                mResDirs.add(dir);
+            }
+        } else {
+            if (bundle->getVerbose()) {
+                fprintf(stderr, "   (ignoring file '%s')\n", subdirName.string());
+            }
+        }
+    }
+
+bail:
+    closedir(dir);
+    dir = NULL;
+
+    if (err != 0) {
+        return err;
+    }
+    return count;
+}
+
+ssize_t
+AaptAssets::slurpResourceZip(Bundle* bundle, const char* filename)
+{
+    int count = 0;
+    SortedVector<AaptGroupEntry> entries;
+
+    ZipFile* zip = new ZipFile;
+    status_t err = zip->open(filename, ZipFile::kOpenReadOnly);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "error opening zip file %s\n", filename);
+        count = err;
+        delete zip;
+        return -1;
+    }
+
+    const int N = zip->getNumEntries();
+    for (int i=0; i<N; i++) {
+        ZipEntry* entry = zip->getEntryByIndex(i);
+        if (entry->getDeleted()) {
+            continue;
+        }
+
+        String8 entryName(entry->getFileName());
+
+        String8 dirName = entryName.getPathDir();
+        sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
+
+        String8 resType;
+        AaptGroupEntry kind;
+
+        String8 remain;
+        if (entryName.walkPath(&remain) == kResourceDir) {
+            // these are the resources, pull their type out of the directory name
+            kind.initFromDirName(remain.walkPath().string(), &resType);
+        } else {
+            // these are untyped and don't have an AaptGroupEntry
+        }
+        if (entries.indexOf(kind) < 0) {
+            entries.add(kind);
+            mGroupEntries.add(kind);
+        }
+
+        // use the one from the zip file if they both exist.
+        dir->removeFile(entryName.getPathLeaf());
+
+        sp<AaptFile> file = new AaptFile(entryName, kind, resType);
+        status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
+        if (err != NO_ERROR) {
+            fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string());
+            count = err;
+            goto bail;
+        }
+        file->setCompressionMethod(entry->getCompressionMethod());
+
+#if 0
+        if (entryName == "AndroidManifest.xml") {
+            printf("AndroidManifest.xml\n");
+        }
+        printf("\n\nfile: %s\n", entryName.string());
+#endif
+
+        size_t len = entry->getUncompressedLen();
+        void* data = zip->uncompress(entry);
+        void* buf = file->editData(len);
+        memcpy(buf, data, len);
+
+#if 0
+        const int OFF = 0;
+        const unsigned char* p = (unsigned char*)data;
+        const unsigned char* end = p+len;
+        p += OFF;
+        for (int i=0; i<32 && p < end; i++) {
+            printf("0x%03x ", i*0x10 + OFF);
+            for (int j=0; j<0x10 && p < end; j++) {
+                printf(" %02x", *p);
+                p++;
+            }
+            printf("\n");
+        }
+#endif
+
+        free(data);
+
+        count++;
+    }
+
+bail:
+    delete zip;
+    return count;
+}
+
+status_t AaptAssets::filter(Bundle* bundle)
+{
+    ResourceFilter reqFilter;
+    status_t err = reqFilter.parse(bundle->getConfigurations());
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    ResourceFilter prefFilter;
+    err = prefFilter.parse(bundle->getPreferredConfigurations());
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    if (reqFilter.isEmpty() && prefFilter.isEmpty()) {
+        return NO_ERROR;
+    }
+
+    if (bundle->getVerbose()) {
+        if (!reqFilter.isEmpty()) {
+            printf("Applying required filter: %s\n",
+                    bundle->getConfigurations());
+        }
+        if (!prefFilter.isEmpty()) {
+            printf("Applying preferred filter: %s\n",
+                    bundle->getPreferredConfigurations());
+        }
+    }
+
+    const Vector<sp<AaptDir> >& resdirs = mResDirs;
+    const size_t ND = resdirs.size();
+    for (size_t i=0; i<ND; i++) {
+        const sp<AaptDir>& dir = resdirs.itemAt(i);
+        if (dir->getLeaf() == kValuesDir) {
+            // The "value" dir is special since a single file defines
+            // multiple resources, so we can not do filtering on the
+            // files themselves.
+            continue;
+        }
+        if (dir->getLeaf() == kMipmapDir) {
+            // We also skip the "mipmap" directory, since the point of this
+            // is to include all densities without stripping.  If you put
+            // other configurations in here as well they won't be stripped
+            // either...  So don't do that.  Seriously.  What is wrong with you?
+            continue;
+        }
+
+        const size_t NG = dir->getFiles().size();
+        for (size_t j=0; j<NG; j++) {
+            sp<AaptGroup> grp = dir->getFiles().valueAt(j);
+
+            // First remove any configurations we know we don't need.
+            for (size_t k=0; k<grp->getFiles().size(); k++) {
+                sp<AaptFile> file = grp->getFiles().valueAt(k);
+                if (k == 0 && grp->getFiles().size() == 1) {
+                    // If this is the only file left, we need to keep it.
+                    // Otherwise the resource IDs we are using will be inconsistent
+                    // with what we get when not stripping.  Sucky, but at least
+                    // for now we can rely on the back-end doing another filtering
+                    // pass to take this out and leave us with this resource name
+                    // containing no entries.
+                    continue;
+                }
+                if (file->getPath().getPathExtension() == ".xml") {
+                    // We can't remove .xml files at this point, because when
+                    // we parse them they may add identifier resources, so
+                    // removing them can cause our resource identifiers to
+                    // become inconsistent.
+                    continue;
+                }
+                const ResTable_config& config(file->getGroupEntry().toParams());
+                if (!reqFilter.match(config)) {
+                    if (bundle->getVerbose()) {
+                        printf("Pruning unneeded resource: %s\n",
+                                file->getPrintableSource().string());
+                    }
+                    grp->removeFile(k);
+                    k--;
+                }
+            }
+
+            // Quick check: no preferred filters, nothing more to do.
+            if (prefFilter.isEmpty()) {
+                continue;
+            }
+
+            // Now deal with preferred configurations.
+            for (int axis=AXIS_START; axis<=AXIS_END; axis++) {
+                for (size_t k=0; k<grp->getFiles().size(); k++) {
+                    sp<AaptFile> file = grp->getFiles().valueAt(k);
+                    if (k == 0 && grp->getFiles().size() == 1) {
+                        // If this is the only file left, we need to keep it.
+                        // Otherwise the resource IDs we are using will be inconsistent
+                        // with what we get when not stripping.  Sucky, but at least
+                        // for now we can rely on the back-end doing another filtering
+                        // pass to take this out and leave us with this resource name
+                        // containing no entries.
+                        continue;
+                    }
+                    if (file->getPath().getPathExtension() == ".xml") {
+                        // We can't remove .xml files at this point, because when
+                        // we parse them they may add identifier resources, so
+                        // removing them can cause our resource identifiers to
+                        // become inconsistent.
+                        continue;
+                    }
+                    const ResTable_config& config(file->getGroupEntry().toParams());
+                    if (!prefFilter.match(axis, config)) {
+                        // This is a resource we would prefer not to have.  Check
+                        // to see if have a similar variation that we would like
+                        // to have and, if so, we can drop it.
+                        for (size_t m=0; m<grp->getFiles().size(); m++) {
+                            if (m == k) continue;
+                            sp<AaptFile> mfile = grp->getFiles().valueAt(m);
+                            const ResTable_config& mconfig(mfile->getGroupEntry().toParams());
+                            if (AaptGroupEntry::configSameExcept(config, mconfig, axis)) {
+                                if (prefFilter.match(axis, mconfig)) {
+                                    if (bundle->getVerbose()) {
+                                        printf("Pruning unneeded resource: %s\n",
+                                                file->getPrintableSource().string());
+                                    }
+                                    grp->removeFile(k);
+                                    k--;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name)
+{
+    sp<AaptSymbols> sym = mSymbols.valueFor(name);
+    if (sym == NULL) {
+        sym = new AaptSymbols();
+        mSymbols.add(name, sym);
+    }
+    return sym;
+}
+
+sp<AaptSymbols> AaptAssets::getJavaSymbolsFor(const String8& name)
+{
+    sp<AaptSymbols> sym = mJavaSymbols.valueFor(name);
+    if (sym == NULL) {
+        sym = new AaptSymbols();
+        mJavaSymbols.add(name, sym);
+    }
+    return sym;
+}
+
+status_t AaptAssets::applyJavaSymbols()
+{
+    size_t N = mJavaSymbols.size();
+    for (size_t i=0; i<N; i++) {
+        const String8& name = mJavaSymbols.keyAt(i);
+        const sp<AaptSymbols>& symbols = mJavaSymbols.valueAt(i);
+        ssize_t pos = mSymbols.indexOfKey(name);
+        if (pos < 0) {
+            SourcePos pos;
+            pos.error("Java symbol dir %s not defined\n", name.string());
+            return UNKNOWN_ERROR;
+        }
+        //printf("**** applying java symbols in dir %s\n", name.string());
+        status_t err = mSymbols.valueAt(pos)->applyJavaSymbols(symbols);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const {
+    //printf("isJavaSymbol %s: public=%d, includePrivate=%d, isJavaSymbol=%d\n",
+    //        sym.name.string(), sym.isPublic ? 1 : 0, includePrivate ? 1 : 0,
+    //        sym.isJavaSymbol ? 1 : 0);
+    if (!mHavePrivateSymbols) return true;
+    if (sym.isPublic) return true;
+    if (includePrivate && sym.isJavaSymbol) return true;
+    return false;
+}
+
+status_t AaptAssets::buildIncludedResources(Bundle* bundle)
+{
+    if (!mHaveIncludedAssets) {
+        // Add in all includes.
+        const Vector<const char*>& incl = bundle->getPackageIncludes();
+        const size_t N=incl.size();
+        for (size_t i=0; i<N; i++) {
+            if (bundle->getVerbose())
+                printf("Including resources from package: %s\n", incl[i]);
+            if (!mIncludedAssets.addAssetPath(String8(incl[i]), NULL)) {
+                fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
+                        incl[i]);
+                return UNKNOWN_ERROR;
+            }
+        }
+        mHaveIncludedAssets = true;
+    }
+
+    return NO_ERROR;
+}
+
+status_t AaptAssets::addIncludedResources(const sp<AaptFile>& file)
+{
+    const ResTable& res = getIncludedResources();
+    // XXX dirty!
+    return const_cast<ResTable&>(res).add(file->getData(), file->getSize(), NULL);
+}
+
+const ResTable& AaptAssets::getIncludedResources() const
+{
+    return mIncludedAssets.getResources(false);
+}
+
+void AaptAssets::print(const String8& prefix) const
+{
+    String8 innerPrefix(prefix);
+    innerPrefix.append("  ");
+    String8 innerInnerPrefix(innerPrefix);
+    innerInnerPrefix.append("  ");
+    printf("%sConfigurations:\n", prefix.string());
+    const size_t N=mGroupEntries.size();
+    for (size_t i=0; i<N; i++) {
+        String8 cname = mGroupEntries.itemAt(i).toDirName(String8());
+        printf("%s %s\n", prefix.string(),
+                cname != "" ? cname.string() : "(default)");
+    }
+
+    printf("\n%sFiles:\n", prefix.string());
+    AaptDir::print(innerPrefix);
+
+    printf("\n%sResource Dirs:\n", prefix.string());
+    const Vector<sp<AaptDir> >& resdirs = mResDirs;
+    const size_t NR = resdirs.size();
+    for (size_t i=0; i<NR; i++) {
+        const sp<AaptDir>& d = resdirs.itemAt(i);
+        printf("%s  Type %s\n", prefix.string(), d->getLeaf().string());
+        d->print(innerInnerPrefix);
+    }
+}
+
+sp<AaptDir> AaptAssets::resDir(const String8& name) const
+{
+    const Vector<sp<AaptDir> >& resdirs = mResDirs;
+    const size_t N = resdirs.size();
+    for (size_t i=0; i<N; i++) {
+        const sp<AaptDir>& d = resdirs.itemAt(i);
+        if (d->getLeaf() == name) {
+            return d;
+        }
+    }
+    return NULL;
+}
+
+bool
+valid_symbol_name(const String8& symbol)
+{
+    static char const * const KEYWORDS[] = {
+        "abstract", "assert", "boolean", "break",
+        "byte", "case", "catch", "char", "class", "const", "continue",
+        "default", "do", "double", "else", "enum", "extends", "final",
+        "finally", "float", "for", "goto", "if", "implements", "import",
+        "instanceof", "int", "interface", "long", "native", "new", "package",
+        "private", "protected", "public", "return", "short", "static",
+        "strictfp", "super", "switch", "synchronized", "this", "throw",
+        "throws", "transient", "try", "void", "volatile", "while",
+        "true", "false", "null",
+        NULL
+    };
+    const char*const* k = KEYWORDS;
+    const char*const s = symbol.string();
+    while (*k) {
+        if (0 == strcmp(s, *k)) {
+            return false;
+        }
+        k++;
+    }
+    return true;
+}
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
new file mode 100644
index 0000000..5cfa913
--- /dev/null
+++ b/tools/aapt/AaptAssets.h
@@ -0,0 +1,633 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Information about assets being operated on.
+//
+#ifndef __AAPT_ASSETS_H
+#define __AAPT_ASSETS_H
+
+#include <stdlib.h>
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/SortedVector.h>
+#include <utils/String8.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include "ZipFile.h"
+
+#include "Bundle.h"
+#include "SourcePos.h"
+
+using namespace android;
+
+
+extern const char * const gDefaultIgnoreAssets;
+extern const char * gUserIgnoreAssets;
+
+bool valid_symbol_name(const String8& str);
+
+class AaptAssets;
+
+enum {
+    AXIS_NONE = 0,
+    AXIS_MCC = 1,
+    AXIS_MNC,
+    AXIS_LANGUAGE,
+    AXIS_REGION,
+    AXIS_SCREENLAYOUTSIZE,
+    AXIS_SCREENLAYOUTLONG,
+    AXIS_ORIENTATION,
+    AXIS_UIMODETYPE,
+    AXIS_UIMODENIGHT,
+    AXIS_DENSITY,
+    AXIS_TOUCHSCREEN,
+    AXIS_KEYSHIDDEN,
+    AXIS_KEYBOARD,
+    AXIS_NAVHIDDEN,
+    AXIS_NAVIGATION,
+    AXIS_SCREENSIZE,
+    AXIS_SMALLESTSCREENWIDTHDP,
+    AXIS_SCREENWIDTHDP,
+    AXIS_SCREENHEIGHTDP,
+    AXIS_LAYOUTDIR,
+    AXIS_VERSION,
+
+    AXIS_START = AXIS_MCC,
+    AXIS_END = AXIS_VERSION,
+};
+
+/**
+ * This structure contains a specific variation of a single file out
+ * of all the variations it can have that we can have.
+ */
+struct AaptGroupEntry
+{
+public:
+    AaptGroupEntry() : mParamsChanged(true) { }
+    AaptGroupEntry(const String8& _locale, const String8& _vendor)
+        : locale(_locale), vendor(_vendor), mParamsChanged(true) { }
+
+    bool initFromDirName(const char* dir, String8* resType);
+
+    static status_t parseNamePart(const String8& part, int* axis, uint32_t* value);
+
+    static uint32_t getConfigValueForAxis(const ResTable_config& config, int axis);
+
+    static bool configSameExcept(const ResTable_config& config,
+            const ResTable_config& otherConfig, int axis);
+
+    static bool getMccName(const char* name, ResTable_config* out = NULL);
+    static bool getMncName(const char* name, ResTable_config* out = NULL);
+    static bool getLocaleName(const char* name, ResTable_config* out = NULL);
+    static bool getScreenLayoutSizeName(const char* name, ResTable_config* out = NULL);
+    static bool getScreenLayoutLongName(const char* name, ResTable_config* out = NULL);
+    static bool getOrientationName(const char* name, ResTable_config* out = NULL);
+    static bool getUiModeTypeName(const char* name, ResTable_config* out = NULL);
+    static bool getUiModeNightName(const char* name, ResTable_config* out = NULL);
+    static bool getDensityName(const char* name, ResTable_config* out = NULL);
+    static bool getTouchscreenName(const char* name, ResTable_config* out = NULL);
+    static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL);
+    static bool getKeyboardName(const char* name, ResTable_config* out = NULL);
+    static bool getNavigationName(const char* name, ResTable_config* out = NULL);
+    static bool getNavHiddenName(const char* name, ResTable_config* out = NULL);
+    static bool getScreenSizeName(const char* name, ResTable_config* out = NULL);
+    static bool getSmallestScreenWidthDpName(const char* name, ResTable_config* out = NULL);
+    static bool getScreenWidthDpName(const char* name, ResTable_config* out = NULL);
+    static bool getScreenHeightDpName(const char* name, ResTable_config* out = NULL);
+    static bool getLayoutDirectionName(const char* name, ResTable_config* out = NULL);
+    static bool getVersionName(const char* name, ResTable_config* out = NULL);
+
+    int compare(const AaptGroupEntry& o) const;
+
+    const ResTable_config& toParams() const;
+
+    inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; }
+    inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; }
+    inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; }
+    inline bool operator!=(const AaptGroupEntry& o) const { return compare(o) != 0; }
+    inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; }
+    inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; }
+
+    String8 toString() const;
+    String8 toDirName(const String8& resType) const;
+
+    const String8& getVersionString() const { return version; }
+
+private:
+    String8 mcc;
+    String8 mnc;
+    String8 locale;
+    String8 vendor;
+    String8 smallestScreenWidthDp;
+    String8 screenWidthDp;
+    String8 screenHeightDp;
+    String8 screenLayoutSize;
+    String8 screenLayoutLong;
+    String8 orientation;
+    String8 uiModeType;
+    String8 uiModeNight;
+    String8 density;
+    String8 touchscreen;
+    String8 keysHidden;
+    String8 keyboard;
+    String8 navHidden;
+    String8 navigation;
+    String8 screenSize;
+    String8 layoutDirection;
+    String8 version;
+
+    mutable bool mParamsChanged;
+    mutable ResTable_config mParams;
+};
+
+inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
+{
+    return lhs.compare(rhs);
+}
+
+inline int strictly_order_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
+{
+    return compare_type(lhs, rhs) < 0;
+}
+
+class AaptGroup;
+class FilePathStore;
+
+/**
+ * A single asset file we know about.
+ */
+class AaptFile : public RefBase
+{
+public:
+    AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry,
+             const String8& resType)
+        : mGroupEntry(groupEntry)
+        , mResourceType(resType)
+        , mSourceFile(sourceFile)
+        , mData(NULL)
+        , mDataSize(0)
+        , mBufferSize(0)
+        , mCompression(ZipEntry::kCompressStored)
+        {
+            //printf("new AaptFile created %s\n", (const char*)sourceFile);
+        }
+    virtual ~AaptFile() {
+        free(mData);
+    }
+
+    const String8& getPath() const { return mPath; }
+    const AaptGroupEntry& getGroupEntry() const { return mGroupEntry; }
+
+    // Data API.  If there is data attached to the file,
+    // getSourceFile() is not used.
+    bool hasData() const { return mData != NULL; }
+    const void* getData() const { return mData; }
+    size_t getSize() const { return mDataSize; }
+    void* editData(size_t size);
+    void* editData(size_t* outSize = NULL);
+    void* padData(size_t wordSize);
+    status_t writeData(const void* data, size_t size);
+    void clearData();
+
+    const String8& getResourceType() const { return mResourceType; }
+
+    // File API.  If the file does not hold raw data, this is
+    // a full path to a file on the filesystem that holds its data.
+    const String8& getSourceFile() const { return mSourceFile; }
+
+    String8 getPrintableSource() const;
+
+    // Desired compression method, as per utils/ZipEntry.h.  For example,
+    // no compression is ZipEntry::kCompressStored.
+    int getCompressionMethod() const { return mCompression; }
+    void setCompressionMethod(int c) { mCompression = c; }
+private:
+    friend class AaptGroup;
+
+    String8 mPath;
+    AaptGroupEntry mGroupEntry;
+    String8 mResourceType;
+    String8 mSourceFile;
+    void* mData;
+    size_t mDataSize;
+    size_t mBufferSize;
+    int mCompression;
+};
+
+/**
+ * A group of related files (the same file, with different
+ * vendor/locale variations).
+ */
+class AaptGroup : public RefBase
+{
+public:
+    AaptGroup(const String8& leaf, const String8& path)
+        : mLeaf(leaf), mPath(path) { }
+    virtual ~AaptGroup() { }
+
+    const String8& getLeaf() const { return mLeaf; }
+
+    // Returns the relative path after the AaptGroupEntry dirs.
+    const String8& getPath() const { return mPath; }
+
+    const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& getFiles() const
+        { return mFiles; }
+
+    status_t addFile(const sp<AaptFile>& file);
+    void removeFile(size_t index);
+
+    void print(const String8& prefix) const;
+
+    String8 getPrintableSource() const;
+
+private:
+    String8 mLeaf;
+    String8 mPath;
+
+    DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > mFiles;
+};
+
+/**
+ * A single directory of assets, which can contain files and other
+ * sub-directories.
+ */
+class AaptDir : public RefBase
+{
+public:
+    AaptDir(const String8& leaf, const String8& path)
+        : mLeaf(leaf), mPath(path) { }
+    virtual ~AaptDir() { }
+
+    const String8& getLeaf() const { return mLeaf; }
+
+    const String8& getPath() const { return mPath; }
+
+    const DefaultKeyedVector<String8, sp<AaptGroup> >& getFiles() const { return mFiles; }
+    const DefaultKeyedVector<String8, sp<AaptDir> >& getDirs() const { return mDirs; }
+
+    virtual status_t addFile(const String8& name, const sp<AaptGroup>& file);
+
+    void removeFile(const String8& name);
+    void removeDir(const String8& name);
+
+    /*
+     * Perform some sanity checks on the names of files and directories here.
+     * In particular:
+     *  - Check for illegal chars in filenames.
+     *  - Check filename length.
+     *  - Check for presence of ".gz" and non-".gz" copies of same file.
+     *  - Check for multiple files whose names match in a case-insensitive
+     *    fashion (problematic for some systems).
+     *
+     * Comparing names against all other names is O(n^2).  We could speed
+     * it up some by sorting the entries and being smarter about what we
+     * compare against, but I'm not expecting to have enough files in a
+     * single directory to make a noticeable difference in speed.
+     *
+     * Note that sorting here is not enough to guarantee that the package
+     * contents are sorted -- subsequent updates can rearrange things.
+     */
+    status_t validate() const;
+
+    void print(const String8& prefix) const;
+
+    String8 getPrintableSource() const;
+
+private:
+    friend class AaptAssets;
+
+    status_t addDir(const String8& name, const sp<AaptDir>& dir);
+    sp<AaptDir> makeDir(const String8& name);
+    status_t addLeafFile(const String8& leafName,
+                         const sp<AaptFile>& file);
+    virtual ssize_t slurpFullTree(Bundle* bundle,
+                                  const String8& srcDir,
+                                  const AaptGroupEntry& kind,
+                                  const String8& resType,
+                                  sp<FilePathStore>& fullResPaths);
+
+    String8 mLeaf;
+    String8 mPath;
+
+    DefaultKeyedVector<String8, sp<AaptGroup> > mFiles;
+    DefaultKeyedVector<String8, sp<AaptDir> > mDirs;
+};
+
+/**
+ * All information we know about a particular symbol.
+ */
+class AaptSymbolEntry
+{
+public:
+    AaptSymbolEntry()
+        : isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN)
+    {
+    }
+    AaptSymbolEntry(const String8& _name)
+        : name(_name), isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN)
+    {
+    }
+    AaptSymbolEntry(const AaptSymbolEntry& o)
+        : name(o.name), sourcePos(o.sourcePos), isPublic(o.isPublic)
+        , isJavaSymbol(o.isJavaSymbol), comment(o.comment), typeComment(o.typeComment)
+        , typeCode(o.typeCode), int32Val(o.int32Val), stringVal(o.stringVal)
+    {
+    }
+    AaptSymbolEntry operator=(const AaptSymbolEntry& o)
+    {
+        sourcePos = o.sourcePos;
+        isPublic = o.isPublic;
+        isJavaSymbol = o.isJavaSymbol;
+        comment = o.comment;
+        typeComment = o.typeComment;
+        typeCode = o.typeCode;
+        int32Val = o.int32Val;
+        stringVal = o.stringVal;
+        return *this;
+    }
+    
+    const String8 name;
+    
+    SourcePos sourcePos;
+    bool isPublic;
+    bool isJavaSymbol;
+    
+    String16 comment;
+    String16 typeComment;
+    
+    enum {
+        TYPE_UNKNOWN = 0,
+        TYPE_INT32,
+        TYPE_STRING
+    };
+    
+    int typeCode;
+    
+    // Value.  May be one of these.
+    int32_t int32Val;
+    String8 stringVal;
+};
+
+/**
+ * A group of related symbols (such as indices into a string block)
+ * that have been generated from the assets.
+ */
+class AaptSymbols : public RefBase
+{
+public:
+    AaptSymbols() { }
+    virtual ~AaptSymbols() { }
+
+    status_t addSymbol(const String8& name, int32_t value, const SourcePos& pos) {
+        if (!check_valid_symbol_name(name, pos, "symbol")) {
+            return BAD_VALUE;
+        }
+        AaptSymbolEntry& sym = edit_symbol(name, &pos);
+        sym.typeCode = AaptSymbolEntry::TYPE_INT32;
+        sym.int32Val = value;
+        return NO_ERROR;
+    }
+
+    status_t addStringSymbol(const String8& name, const String8& value,
+            const SourcePos& pos) {
+        if (!check_valid_symbol_name(name, pos, "symbol")) {
+            return BAD_VALUE;
+        }
+        AaptSymbolEntry& sym = edit_symbol(name, &pos);
+        sym.typeCode = AaptSymbolEntry::TYPE_STRING;
+        sym.stringVal = value;
+        return NO_ERROR;
+    }
+
+    status_t makeSymbolPublic(const String8& name, const SourcePos& pos) {
+        if (!check_valid_symbol_name(name, pos, "symbol")) {
+            return BAD_VALUE;
+        }
+        AaptSymbolEntry& sym = edit_symbol(name, &pos);
+        sym.isPublic = true;
+        return NO_ERROR;
+    }
+
+    status_t makeSymbolJavaSymbol(const String8& name, const SourcePos& pos) {
+        if (!check_valid_symbol_name(name, pos, "symbol")) {
+            return BAD_VALUE;
+        }
+        AaptSymbolEntry& sym = edit_symbol(name, &pos);
+        sym.isJavaSymbol = true;
+        return NO_ERROR;
+    }
+
+    void appendComment(const String8& name, const String16& comment, const SourcePos& pos) {
+        if (comment.size() <= 0) {
+            return;
+        }
+        AaptSymbolEntry& sym = edit_symbol(name, &pos);
+        if (sym.comment.size() == 0) {
+            sym.comment = comment;
+        } else {
+            sym.comment.append(String16("\n"));
+            sym.comment.append(comment);
+        }
+    }
+
+    void appendTypeComment(const String8& name, const String16& comment) {
+        if (comment.size() <= 0) {
+            return;
+        }
+        AaptSymbolEntry& sym = edit_symbol(name, NULL);
+        if (sym.typeComment.size() == 0) {
+            sym.typeComment = comment;
+        } else {
+            sym.typeComment.append(String16("\n"));
+            sym.typeComment.append(comment);
+        }
+    }
+    
+    sp<AaptSymbols> addNestedSymbol(const String8& name, const SourcePos& pos) {
+        if (!check_valid_symbol_name(name, pos, "nested symbol")) {
+            return NULL;
+        }
+        
+        sp<AaptSymbols> sym = mNestedSymbols.valueFor(name);
+        if (sym == NULL) {
+            sym = new AaptSymbols();
+            mNestedSymbols.add(name, sym);
+        }
+
+        return sym;
+    }
+
+    status_t applyJavaSymbols(const sp<AaptSymbols>& javaSymbols);
+
+    const KeyedVector<String8, AaptSymbolEntry>& getSymbols() const
+        { return mSymbols; }
+    const DefaultKeyedVector<String8, sp<AaptSymbols> >& getNestedSymbols() const
+        { return mNestedSymbols; }
+
+    const String16& getComment(const String8& name) const
+        { return get_symbol(name).comment; }
+    const String16& getTypeComment(const String8& name) const
+        { return get_symbol(name).typeComment; }
+
+private:
+    bool check_valid_symbol_name(const String8& symbol, const SourcePos& pos, const char* label) {
+        if (valid_symbol_name(symbol)) {
+            return true;
+        }
+        pos.error("invalid %s: '%s'\n", label, symbol.string());
+        return false;
+    }
+    AaptSymbolEntry& edit_symbol(const String8& symbol, const SourcePos* pos) {
+        ssize_t i = mSymbols.indexOfKey(symbol);
+        if (i < 0) {
+            i = mSymbols.add(symbol, AaptSymbolEntry(symbol));
+        }
+        AaptSymbolEntry& sym = mSymbols.editValueAt(i);
+        if (pos != NULL && sym.sourcePos.line < 0) {
+            sym.sourcePos = *pos;
+        }
+        return sym;
+    }
+    const AaptSymbolEntry& get_symbol(const String8& symbol) const {
+        ssize_t i = mSymbols.indexOfKey(symbol);
+        if (i >= 0) {
+            return mSymbols.valueAt(i);
+        }
+        return mDefSymbol;
+    }
+
+    KeyedVector<String8, AaptSymbolEntry>           mSymbols;
+    DefaultKeyedVector<String8, sp<AaptSymbols> >   mNestedSymbols;
+    AaptSymbolEntry                                 mDefSymbol;
+};
+
+class ResourceTypeSet : public RefBase,
+                        public KeyedVector<String8,sp<AaptGroup> >
+{
+public:
+    ResourceTypeSet();
+};
+
+// Storage for lists of fully qualified paths for
+// resources encountered during slurping.
+class FilePathStore : public RefBase,
+                      public Vector<String8>
+{
+public:
+    FilePathStore();
+};
+
+/**
+ * Asset hierarchy being operated on.
+ */
+class AaptAssets : public AaptDir
+{
+public:
+    AaptAssets();
+    virtual ~AaptAssets() { delete mRes; }
+
+    const String8& getPackage() const { return mPackage; }
+    void setPackage(const String8& package) {
+        mPackage = package;
+        mSymbolsPrivatePackage = package;
+        mHavePrivateSymbols = false;
+    }
+
+    const SortedVector<AaptGroupEntry>& getGroupEntries() const;
+
+    virtual status_t addFile(const String8& name, const sp<AaptGroup>& file);
+
+    sp<AaptFile> addFile(const String8& filePath,
+                         const AaptGroupEntry& entry,
+                         const String8& srcDir,
+                         sp<AaptGroup>* outGroup,
+                         const String8& resType);
+
+    void addResource(const String8& leafName,
+                     const String8& path,
+                     const sp<AaptFile>& file,
+                     const String8& resType);
+
+    void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); }
+    
+    ssize_t slurpFromArgs(Bundle* bundle);
+
+    sp<AaptSymbols> getSymbolsFor(const String8& name);
+
+    sp<AaptSymbols> getJavaSymbolsFor(const String8& name);
+
+    status_t applyJavaSymbols();
+
+    const DefaultKeyedVector<String8, sp<AaptSymbols> >& getSymbols() const { return mSymbols; }
+
+    String8 getSymbolsPrivatePackage() const { return mSymbolsPrivatePackage; }
+    void setSymbolsPrivatePackage(const String8& pkg) {
+        mSymbolsPrivatePackage = pkg;
+        mHavePrivateSymbols = mSymbolsPrivatePackage != mPackage;
+    }
+
+    bool havePrivateSymbols() const { return mHavePrivateSymbols; }
+
+    bool isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const;
+
+    status_t buildIncludedResources(Bundle* bundle);
+    status_t addIncludedResources(const sp<AaptFile>& file);
+    const ResTable& getIncludedResources() const;
+
+    void print(const String8& prefix) const;
+
+    inline const Vector<sp<AaptDir> >& resDirs() const { return mResDirs; }
+    sp<AaptDir> resDir(const String8& name) const;
+
+    inline sp<AaptAssets> getOverlay() { return mOverlay; }
+    inline void setOverlay(sp<AaptAssets>& overlay) { mOverlay = overlay; }
+    
+    inline KeyedVector<String8, sp<ResourceTypeSet> >* getResources() { return mRes; }
+    inline void 
+        setResources(KeyedVector<String8, sp<ResourceTypeSet> >* res) { delete mRes; mRes = res; }
+
+    inline sp<FilePathStore>& getFullResPaths() { return mFullResPaths; }
+    inline void
+        setFullResPaths(sp<FilePathStore>& res) { mFullResPaths = res; }
+
+    inline sp<FilePathStore>& getFullAssetPaths() { return mFullAssetPaths; }
+    inline void
+        setFullAssetPaths(sp<FilePathStore>& res) { mFullAssetPaths = res; }
+
+private:
+    virtual ssize_t slurpFullTree(Bundle* bundle,
+                                  const String8& srcDir,
+                                  const AaptGroupEntry& kind,
+                                  const String8& resType,
+                                  sp<FilePathStore>& fullResPaths);
+
+    ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir);
+    ssize_t slurpResourceZip(Bundle* bundle, const char* filename);
+
+    status_t filter(Bundle* bundle);
+
+    String8 mPackage;
+    SortedVector<AaptGroupEntry> mGroupEntries;
+    DefaultKeyedVector<String8, sp<AaptSymbols> > mSymbols;
+    DefaultKeyedVector<String8, sp<AaptSymbols> > mJavaSymbols;
+    String8 mSymbolsPrivatePackage;
+    bool mHavePrivateSymbols;
+
+    Vector<sp<AaptDir> > mResDirs;
+
+    bool mChanged;
+
+    bool mHaveIncludedAssets;
+    AssetManager mIncludedAssets;
+
+    sp<AaptAssets> mOverlay;
+    KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
+
+    sp<FilePathStore> mFullResPaths;
+    sp<FilePathStore> mFullAssetPaths;
+};
+
+#endif // __AAPT_ASSETS_H
+
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
new file mode 100644
index 0000000..452c60a
--- /dev/null
+++ b/tools/aapt/Android.mk
@@ -0,0 +1,103 @@
+# 
+# Copyright 2006 The Android Open Source Project
+#
+# Android Asset Packaging Tool
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+
+aapt_src_files := \
+	AaptAssets.cpp \
+	Command.cpp \
+	CrunchCache.cpp \
+	FileFinder.cpp \
+	Main.cpp \
+	Package.cpp \
+	StringPool.cpp \
+	XMLNode.cpp \
+	ResourceFilter.cpp \
+	ResourceIdCache.cpp \
+	ResourceTable.cpp \
+	Images.cpp \
+	Resource.cpp \
+    pseudolocalize.cpp \
+    SourcePos.cpp \
+	WorkQueue.cpp \
+    ZipEntry.cpp \
+    ZipFile.cpp \
+	qsort_r_compat.c
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(aapt_src_files)
+
+LOCAL_CFLAGS += -Wno-format-y2k
+ifeq (darwin,$(HOST_OS))
+LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS
+endif
+
+LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
+
+LOCAL_C_INCLUDES += external/libpng
+LOCAL_C_INCLUDES += external/zlib
+
+LOCAL_STATIC_LIBRARIES := \
+	libandroidfw \
+	libutils \
+	libcutils \
+	libexpat \
+	libpng \
+	liblog
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -lrt -ldl -lpthread
+endif
+
+# Statically link libz for MinGW (Win SDK under Linux),
+# and dynamically link for all others.
+ifneq ($(strip $(USE_MINGW)),)
+  LOCAL_STATIC_LIBRARIES += libz
+else
+  LOCAL_LDLIBS += -lz
+endif
+
+LOCAL_MODULE := aapt
+
+include $(BUILD_HOST_EXECUTABLE)
+
+# aapt for running on the device
+# =========================================================
+ifneq ($(SDK_ONLY),true)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(aapt_src_files)
+
+LOCAL_MODULE := aapt
+
+LOCAL_C_INCLUDES += bionic
+LOCAL_C_INCLUDES += bionic/libstdc++/include
+LOCAL_C_INCLUDES += external/stlport/stlport
+LOCAL_C_INCLUDES += external/libpng
+LOCAL_C_INCLUDES += external/zlib
+
+LOCAL_CFLAGS += -Wno-non-virtual-dtor
+
+LOCAL_SHARED_LIBRARIES := \
+        libandroidfw \
+        libutils \
+        libcutils \
+        libpng \
+        liblog \
+        libz
+
+LOCAL_STATIC_LIBRARIES := \
+        libstlport_static \
+        libexpat_static
+
+include $(BUILD_EXECUTABLE)
+endif
+
+endif # TARGET_BUILD_APPS
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
new file mode 100644
index 0000000..b67ca09
--- /dev/null
+++ b/tools/aapt/Bundle.h
@@ -0,0 +1,309 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// State bundle.  Used to pass around stuff like command-line args.
+//
+#ifndef __BUNDLE_H
+#define __BUNDLE_H
+
+#include <stdlib.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+enum {
+    SDK_CUPCAKE = 3,
+    SDK_DONUT = 4,
+    SDK_ECLAIR = 5,
+    SDK_ECLAIR_0_1 = 6,
+    SDK_MR1 = 7,
+    SDK_FROYO = 8,
+    SDK_HONEYCOMB_MR2 = 13,
+    SDK_ICE_CREAM_SANDWICH = 14,
+    SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+};
+
+/*
+ * Things we can do.
+ */
+typedef enum Command {
+    kCommandUnknown = 0,
+    kCommandVersion,
+    kCommandList,
+    kCommandDump,
+    kCommandAdd,
+    kCommandRemove,
+    kCommandPackage,
+    kCommandCrunch,
+    kCommandSingleCrunch,
+} Command;
+
+/*
+ * Bundle of goodies, including everything specified on the command line.
+ */
+class Bundle {
+public:
+    Bundle(void)
+        : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false),
+          mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
+          mUpdate(false), mExtending(false),
+          mRequireLocalization(false), mPseudolocalize(false),
+          mWantUTF16(false), mValues(false), mIncludeMetaData(false),
+          mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
+          mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
+          mAutoAddOverlay(false), mGenDependencies(false),
+          mAssetSourceDir(NULL), 
+          mCrunchedOutputDir(NULL), mProguardFile(NULL),
+          mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
+          mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
+          mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL),
+          mVersionCode(NULL), mVersionName(NULL), mCustomPackage(NULL), mExtraPackages(NULL),
+          mMaxResVersion(NULL), mDebugMode(false), mNonConstantId(false), mProduct(NULL),
+          mUseCrunchCache(false), mErrorOnFailedInsert(false), mOutputTextSymbols(NULL),
+          mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL),
+          mArgc(0), mArgv(NULL)
+        {}
+    ~Bundle(void) {}
+
+    /*
+     * Set the command value.  Returns "false" if it was previously set.
+     */
+    Command getCommand(void) const { return mCmd; }
+    void setCommand(Command cmd) { mCmd = cmd; }
+
+    /*
+     * Command modifiers.  Not all modifiers are appropriate for all
+     * commands.
+     */
+    bool getVerbose(void) const { return mVerbose; }
+    void setVerbose(bool val) { mVerbose = val; }
+    bool getAndroidList(void) const { return mAndroidList; }
+    void setAndroidList(bool val) { mAndroidList = val; }
+    bool getForce(void) const { return mForce; }
+    void setForce(bool val) { mForce = val; }
+    void setGrayscaleTolerance(int val) { mGrayscaleTolerance = val; }
+    int  getGrayscaleTolerance() const { return mGrayscaleTolerance; }
+    bool getMakePackageDirs(void) const { return mMakePackageDirs; }
+    void setMakePackageDirs(bool val) { mMakePackageDirs = val; }
+    bool getUpdate(void) const { return mUpdate; }
+    void setUpdate(bool val) { mUpdate = val; }
+    bool getExtending(void) const { return mExtending; }
+    void setExtending(bool val) { mExtending = val; }
+    bool getRequireLocalization(void) const { return mRequireLocalization; }
+    void setRequireLocalization(bool val) { mRequireLocalization = val; }
+    bool getPseudolocalize(void) const { return mPseudolocalize; }
+    void setPseudolocalize(bool val) { mPseudolocalize = val; }
+    void setWantUTF16(bool val) { mWantUTF16 = val; }
+    bool getValues(void) const { return mValues; }
+    void setValues(bool val) { mValues = val; }
+    bool getIncludeMetaData(void) const { return mIncludeMetaData; }
+    void setIncludeMetaData(bool val) { mIncludeMetaData = val; }
+    int getCompressionMethod(void) const { return mCompressionMethod; }
+    void setCompressionMethod(int val) { mCompressionMethod = val; }
+    bool getJunkPath(void) const { return mJunkPath; }
+    void setJunkPath(bool val) { mJunkPath = val; }
+    const char* getOutputAPKFile() const { return mOutputAPKFile; }
+    void setOutputAPKFile(const char* val) { mOutputAPKFile = val; }
+    const char* getManifestPackageNameOverride() const { return mManifestPackageNameOverride; }
+    void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; }
+    const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; }
+    void setInstrumentationPackageNameOverride(const char * val) { mInstrumentationPackageNameOverride = val; }
+    bool getAutoAddOverlay() { return mAutoAddOverlay; }
+    void setAutoAddOverlay(bool val) { mAutoAddOverlay = val; }
+    bool getGenDependencies() { return mGenDependencies; }
+    void setGenDependencies(bool val) { mGenDependencies = val; }
+    bool getErrorOnFailedInsert() { return mErrorOnFailedInsert; }
+    void setErrorOnFailedInsert(bool val) { mErrorOnFailedInsert = val; }
+
+    bool getUTF16StringsOption() {
+        return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO);
+    }
+
+    /*
+     * Input options.
+     */
+    const char* getAssetSourceDir() const { return mAssetSourceDir; }
+    void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; }
+    const char* getCrunchedOutputDir() const { return mCrunchedOutputDir; }
+    void setCrunchedOutputDir(const char* dir) { mCrunchedOutputDir = dir; }
+    const char* getProguardFile() const { return mProguardFile; }
+    void setProguardFile(const char* file) { mProguardFile = file; }
+    const android::Vector<const char*>& getResourceSourceDirs() const { return mResourceSourceDirs; }
+    void addResourceSourceDir(const char* dir) { mResourceSourceDirs.insertAt(dir,0); }
+    const char* getAndroidManifestFile() const { return mAndroidManifestFile; }
+    void setAndroidManifestFile(const char* file) { mAndroidManifestFile = file; }
+    const char* getPublicOutputFile() const { return mPublicOutputFile; }
+    void setPublicOutputFile(const char* file) { mPublicOutputFile = file; }
+    const char* getRClassDir() const { return mRClassDir; }
+    void setRClassDir(const char* dir) { mRClassDir = dir; }
+    const char* getConfigurations() const { return mConfigurations.size() > 0 ? mConfigurations.string() : NULL; }
+    void addConfigurations(const char* val) { if (mConfigurations.size() > 0) { mConfigurations.append(","); mConfigurations.append(val); } else { mConfigurations = val; } }
+    const char* getPreferredConfigurations() const { return mPreferredConfigurations.size() > 0 ? mPreferredConfigurations.string() : NULL; }
+    void addPreferredConfigurations(const char* val) { if (mPreferredConfigurations.size() > 0) { mPreferredConfigurations.append(","); mPreferredConfigurations.append(val); } else { mPreferredConfigurations = val; } }
+    const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; }
+    void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; }
+    const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; }
+    void addPackageInclude(const char* file) { mPackageIncludes.add(file); }
+    const android::Vector<const char*>& getJarFiles() const { return mJarFiles; }
+    void addJarFile(const char* file) { mJarFiles.add(file); }
+    const android::Vector<const char*>& getNoCompressExtensions() const { return mNoCompressExtensions; }
+    void addNoCompressExtension(const char* ext) { mNoCompressExtensions.add(ext); }
+
+    const char*  getManifestMinSdkVersion() const { return mManifestMinSdkVersion; }
+    void setManifestMinSdkVersion(const char*  val) { mManifestMinSdkVersion = val; }
+    const char*  getMinSdkVersion() const { return mMinSdkVersion; }
+    void setMinSdkVersion(const char*  val) { mMinSdkVersion = val; }
+    const char*  getTargetSdkVersion() const { return mTargetSdkVersion; }
+    void setTargetSdkVersion(const char*  val) { mTargetSdkVersion = val; }
+    const char*  getMaxSdkVersion() const { return mMaxSdkVersion; }
+    void setMaxSdkVersion(const char*  val) { mMaxSdkVersion = val; }
+    const char*  getVersionCode() const { return mVersionCode; }
+    void setVersionCode(const char*  val) { mVersionCode = val; }
+    const char* getVersionName() const { return mVersionName; }
+    void setVersionName(const char* val) { mVersionName = val; }
+    const char* getCustomPackage() const { return mCustomPackage; }
+    void setCustomPackage(const char* val) { mCustomPackage = val; }
+    const char* getExtraPackages() const { return mExtraPackages; }
+    void setExtraPackages(const char* val) { mExtraPackages = val; }
+    const char* getMaxResVersion() const { return mMaxResVersion; }
+    void setMaxResVersion(const char * val) { mMaxResVersion = val; }
+    bool getDebugMode() const { return mDebugMode; }
+    void setDebugMode(bool val) { mDebugMode = val; }
+    bool getNonConstantId() const { return mNonConstantId; }
+    void setNonConstantId(bool val) { mNonConstantId = val; }
+    const char* getProduct() const { return mProduct; }
+    void setProduct(const char * val) { mProduct = val; }
+    void setUseCrunchCache(bool val) { mUseCrunchCache = val; }
+    bool getUseCrunchCache() const { return mUseCrunchCache; }
+    const char* getOutputTextSymbols() const { return mOutputTextSymbols; }
+    void setOutputTextSymbols(const char* val) { mOutputTextSymbols = val; }
+    const char* getSingleCrunchInputFile() const { return mSingleCrunchInputFile; }
+    void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; }
+    const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; }
+    void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; }
+
+    /*
+     * Set and get the file specification.
+     *
+     * Note this does NOT make a copy of argv.
+     */
+    void setFileSpec(char* const argv[], int argc) {
+        mArgc = argc;
+        mArgv = argv;
+    }
+    int getFileSpecCount(void) const { return mArgc; }
+    const char* getFileSpecEntry(int idx) const { return mArgv[idx]; }
+    void eatArgs(int n) {
+        if (n > mArgc) n = mArgc;
+        mArgv += n;
+        mArgc -= n;
+    }
+
+#if 0
+    /*
+     * Package count.  Nothing to do with anything else here; this is
+     * just a convenient place to stuff it so we don't have to pass it
+     * around everywhere.
+     */
+    int getPackageCount(void) const { return mPackageCount; }
+    void setPackageCount(int val) { mPackageCount = val; }
+#endif
+
+    /* Certain features may only be available on a specific SDK level or
+     * above. SDK levels that have a non-numeric identifier are assumed
+     * to be newer than any SDK level that has a number designated.
+     */
+    bool isMinSdkAtLeast(int desired) {
+        /* If the application specifies a minSdkVersion in the manifest
+         * then use that. Otherwise, check what the user specified on
+         * the command line. If neither, it's not available since
+         * the minimum SDK version is assumed to be 1.
+         */
+        const char *minVer;
+        if (mManifestMinSdkVersion != NULL) {
+            minVer = mManifestMinSdkVersion;
+        } else if (mMinSdkVersion != NULL) {
+            minVer = mMinSdkVersion;
+        } else {
+            return false;
+        }
+
+        char *end;
+        int minSdkNum = (int)strtol(minVer, &end, 0);
+        if (*end == '\0') {
+            if (minSdkNum < desired) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+private:
+    /* commands & modifiers */
+    Command     mCmd;
+    bool        mVerbose;
+    bool        mAndroidList;
+    bool        mForce;
+    int         mGrayscaleTolerance;
+    bool        mMakePackageDirs;
+    bool        mUpdate;
+    bool        mExtending;
+    bool        mRequireLocalization;
+    bool        mPseudolocalize;
+    bool        mWantUTF16;
+    bool        mValues;
+    bool        mIncludeMetaData;
+    int         mCompressionMethod;
+    bool        mJunkPath;
+    const char* mOutputAPKFile;
+    const char* mManifestPackageNameOverride;
+    const char* mInstrumentationPackageNameOverride;
+    bool        mAutoAddOverlay;
+    bool        mGenDependencies;
+    const char* mAssetSourceDir;
+    const char* mCrunchedOutputDir;
+    const char* mProguardFile;
+    const char* mAndroidManifestFile;
+    const char* mPublicOutputFile;
+    const char* mRClassDir;
+    const char* mResourceIntermediatesDir;
+    android::String8 mConfigurations;
+    android::String8 mPreferredConfigurations;
+    android::Vector<const char*> mPackageIncludes;
+    android::Vector<const char*> mJarFiles;
+    android::Vector<const char*> mNoCompressExtensions;
+    android::Vector<const char*> mResourceSourceDirs;
+
+    const char* mManifestMinSdkVersion;
+    const char* mMinSdkVersion;
+    const char* mTargetSdkVersion;
+    const char* mMaxSdkVersion;
+    const char* mVersionCode;
+    const char* mVersionName;
+    const char* mCustomPackage;
+    const char* mExtraPackages;
+    const char* mMaxResVersion;
+    bool        mDebugMode;
+    bool        mNonConstantId;
+    const char* mProduct;
+    bool        mUseCrunchCache;
+    bool        mErrorOnFailedInsert;
+    const char* mOutputTextSymbols;
+    const char* mSingleCrunchInputFile;
+    const char* mSingleCrunchOutputFile;
+
+    /* file specification */
+    int         mArgc;
+    char* const* mArgv;
+
+#if 0
+    /* misc stuff */
+    int         mPackageCount;
+#endif
+
+};
+
+#endif // __BUNDLE_H
diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h
new file mode 100644
index 0000000..0e65589
--- /dev/null
+++ b/tools/aapt/CacheUpdater.h
@@ -0,0 +1,107 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Abstraction of calls to system to make directories and delete files and
+// wrapper to image processing.
+
+#ifndef CACHE_UPDATER_H
+#define CACHE_UPDATER_H
+
+#include <utils/String8.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include "Images.h"
+
+using namespace android;
+
+/** CacheUpdater
+ *  This is a pure virtual class that declares abstractions of functions useful
+ *  for managing a cache files. This manager is set up to be used in a
+ *  mirror cache where the source tree is duplicated and filled with processed
+ *  images. This class is abstracted to allow for dependency injection during
+ *  unit testing.
+ *  Usage:
+ *      To update/add a file to the cache, call processImage
+ *      To remove a file from the cache, call deleteFile
+ */
+class CacheUpdater {
+public:
+    // Make sure all the directories along this path exist
+    virtual void ensureDirectoriesExist(String8 path) = 0;
+
+    // Delete a file
+    virtual void deleteFile(String8 path) = 0;
+
+    // Process an image from source out to dest
+    virtual void processImage(String8 source, String8 dest) = 0;
+private:
+};
+
+/** SystemCacheUpdater
+ * This is an implementation of the above virtual cache updater specification.
+ * This implementations hits the filesystem to manage a cache and calls out to
+ * the PNG crunching in images.h to process images out to its cache components.
+ */
+class SystemCacheUpdater : public CacheUpdater {
+public:
+    // Constructor to set bundle to pass to preProcessImage
+    SystemCacheUpdater (Bundle* b)
+        : bundle(b) { };
+
+    // Make sure all the directories along this path exist
+    virtual void ensureDirectoriesExist(String8 path)
+    {
+        // Check to see if we're dealing with a fully qualified path
+        String8 existsPath;
+        String8 toCreate;
+        String8 remains;
+        struct stat s;
+
+        // Check optomistically to see if all directories exist.
+        // If something in the path doesn't exist, then walk the path backwards
+        // and find the place to start creating directories forward.
+        if (stat(path.string(),&s) == -1) {
+            // Walk backwards to find place to start creating directories
+            existsPath = path;
+            do {
+                // As we remove the end of existsPath add it to
+                // the string of paths to create.
+                toCreate = existsPath.getPathLeaf().appendPath(toCreate);
+                existsPath = existsPath.getPathDir();
+            } while (stat(existsPath.string(),&s) == -1);
+
+            // Walk forwards and build directories as we go
+            do {
+                // Advance to the next segment of the path
+                existsPath.appendPath(toCreate.walkPath(&remains));
+                toCreate = remains;
+#ifdef HAVE_MS_C_RUNTIME
+                _mkdir(existsPath.string());
+#else
+                mkdir(existsPath.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+            } while (remains.length() > 0);
+        } //if
+    };
+
+    // Delete a file
+    virtual void deleteFile(String8 path)
+    {
+        if (remove(path.string()) != 0)
+            fprintf(stderr,"ERROR DELETING %s\n",path.string());
+    };
+
+    // Process an image from source out to dest
+    virtual void processImage(String8 source, String8 dest)
+    {
+        // Make sure we're trying to write to a directory that is extant
+        ensureDirectoriesExist(dest.getPathDir());
+
+        preProcessImageToCache(bundle, source, dest);
+    };
+private:
+    Bundle* bundle;
+};
+
+#endif // CACHE_UPDATER_H
\ No newline at end of file
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
new file mode 100644
index 0000000..7fa8c9d
--- /dev/null
+++ b/tools/aapt/Command.cpp
@@ -0,0 +1,2121 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Android Asset Packaging Tool main entry point.
+//
+#include "Main.h"
+#include "Bundle.h"
+#include "ResourceFilter.h"
+#include "ResourceTable.h"
+#include "Images.h"
+#include "XMLNode.h"
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+
+#include <fcntl.h>
+#include <errno.h>
+
+using namespace android;
+
+/*
+ * Show version info.  All the cool kids do it.
+ */
+int doVersion(Bundle* bundle)
+{
+    if (bundle->getFileSpecCount() != 0) {
+        printf("(ignoring extra arguments)\n");
+    }
+    printf("Android Asset Packaging Tool, v0.2\n");
+
+    return 0;
+}
+
+
+/*
+ * Open the file read only.  The call fails if the file doesn't exist.
+ *
+ * Returns NULL on failure.
+ */
+ZipFile* openReadOnly(const char* fileName)
+{
+    ZipFile* zip;
+    status_t result;
+
+    zip = new ZipFile;
+    result = zip->open(fileName, ZipFile::kOpenReadOnly);
+    if (result != NO_ERROR) {
+        if (result == NAME_NOT_FOUND) {
+            fprintf(stderr, "ERROR: '%s' not found\n", fileName);
+        } else if (result == PERMISSION_DENIED) {
+            fprintf(stderr, "ERROR: '%s' access denied\n", fileName);
+        } else {
+            fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n",
+                fileName);
+        }
+        delete zip;
+        return NULL;
+    }
+
+    return zip;
+}
+
+/*
+ * Open the file read-write.  The file will be created if it doesn't
+ * already exist and "okayToCreate" is set.
+ *
+ * Returns NULL on failure.
+ */
+ZipFile* openReadWrite(const char* fileName, bool okayToCreate)
+{
+    ZipFile* zip = NULL;
+    status_t result;
+    int flags;
+
+    flags = ZipFile::kOpenReadWrite;
+    if (okayToCreate) {
+        flags |= ZipFile::kOpenCreate;
+    }
+
+    zip = new ZipFile;
+    result = zip->open(fileName, flags);
+    if (result != NO_ERROR) {
+        delete zip;
+        zip = NULL;
+        goto bail;
+    }
+
+bail:
+    return zip;
+}
+
+
+/*
+ * Return a short string describing the compression method.
+ */
+const char* compressionName(int method)
+{
+    if (method == ZipEntry::kCompressStored) {
+        return "Stored";
+    } else if (method == ZipEntry::kCompressDeflated) {
+        return "Deflated";
+    } else {
+        return "Unknown";
+    }
+}
+
+/*
+ * Return the percent reduction in size (0% == no compression).
+ */
+int calcPercent(long uncompressedLen, long compressedLen)
+{
+    if (!uncompressedLen) {
+        return 0;
+    } else {
+        return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5);
+    }
+}
+
+/*
+ * Handle the "list" command, which can be a simple file dump or
+ * a verbose listing.
+ *
+ * The verbose listing closely matches the output of the Info-ZIP "unzip"
+ * command.
+ */
+int doList(Bundle* bundle)
+{
+    int result = 1;
+    ZipFile* zip = NULL;
+    const ZipEntry* entry;
+    long totalUncLen, totalCompLen;
+    const char* zipFileName;
+
+    if (bundle->getFileSpecCount() != 1) {
+        fprintf(stderr, "ERROR: specify zip file name (only)\n");
+        goto bail;
+    }
+    zipFileName = bundle->getFileSpecEntry(0);
+
+    zip = openReadOnly(zipFileName);
+    if (zip == NULL) {
+        goto bail;
+    }
+
+    int count, i;
+
+    if (bundle->getVerbose()) {
+        printf("Archive:  %s\n", zipFileName);
+        printf(
+            " Length   Method    Size  Ratio   Offset      Date  Time  CRC-32    Name\n");
+        printf(
+            "--------  ------  ------- -----  -------      ----  ----  ------    ----\n");
+    }
+
+    totalUncLen = totalCompLen = 0;
+
+    count = zip->getNumEntries();
+    for (i = 0; i < count; i++) {
+        entry = zip->getEntryByIndex(i);
+        if (bundle->getVerbose()) {
+            char dateBuf[32];
+            time_t when;
+
+            when = entry->getModWhen();
+            strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M",
+                localtime(&when));
+
+            printf("%8ld  %-7.7s %7ld %3d%%  %8zd  %s  %08lx  %s\n",
+                (long) entry->getUncompressedLen(),
+                compressionName(entry->getCompressionMethod()),
+                (long) entry->getCompressedLen(),
+                calcPercent(entry->getUncompressedLen(),
+                            entry->getCompressedLen()),
+                (size_t) entry->getLFHOffset(),
+                dateBuf,
+                entry->getCRC32(),
+                entry->getFileName());
+        } else {
+            printf("%s\n", entry->getFileName());
+        }
+
+        totalUncLen += entry->getUncompressedLen();
+        totalCompLen += entry->getCompressedLen();
+    }
+
+    if (bundle->getVerbose()) {
+        printf(
+        "--------          -------  ---                            -------\n");
+        printf("%8ld          %7ld  %2d%%                            %d files\n",
+            totalUncLen,
+            totalCompLen,
+            calcPercent(totalUncLen, totalCompLen),
+            zip->getNumEntries());
+    }
+
+    if (bundle->getAndroidList()) {
+        AssetManager assets;
+        if (!assets.addAssetPath(String8(zipFileName), NULL)) {
+            fprintf(stderr, "ERROR: list -a failed because assets could not be loaded\n");
+            goto bail;
+        }
+
+        const ResTable& res = assets.getResources(false);
+        if (&res == NULL) {
+            printf("\nNo resource table found.\n");
+        } else {
+#ifndef HAVE_ANDROID_OS
+            printf("\nResource table:\n");
+            res.print(false);
+#endif
+        }
+
+        Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
+                                                   Asset::ACCESS_BUFFER);
+        if (manifestAsset == NULL) {
+            printf("\nNo AndroidManifest.xml found.\n");
+        } else {
+            printf("\nAndroid manifest:\n");
+            ResXMLTree tree;
+            tree.setTo(manifestAsset->getBuffer(true),
+                       manifestAsset->getLength());
+            printXMLBlock(&tree);
+        }
+        delete manifestAsset;
+    }
+
+    result = 0;
+
+bail:
+    delete zip;
+    return result;
+}
+
+static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes)
+{
+    size_t N = tree.getAttributeCount();
+    for (size_t i=0; i<N; i++) {
+        if (tree.getAttributeNameResID(i) == attrRes) {
+            return (ssize_t)i;
+        }
+    }
+    return -1;
+}
+
+String8 getAttribute(const ResXMLTree& tree, const char* ns,
+                            const char* attr, String8* outError)
+{
+    ssize_t idx = tree.indexOfAttribute(ns, attr);
+    if (idx < 0) {
+        return String8();
+    }
+    Res_value value;
+    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+        if (value.dataType != Res_value::TYPE_STRING) {
+            if (outError != NULL) {
+                *outError = "attribute is not a string value";
+            }
+            return String8();
+        }
+    }
+    size_t len;
+    const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+    return str ? String8(str, len) : String8();
+}
+
+static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
+{
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return String8();
+    }
+    Res_value value;
+    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+        if (value.dataType != Res_value::TYPE_STRING) {
+            if (outError != NULL) {
+                *outError = "attribute is not a string value";
+            }
+            return String8();
+        }
+    }
+    size_t len;
+    const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+    return str ? String8(str, len) : String8();
+}
+
+static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes,
+        String8* outError, int32_t defValue = -1)
+{
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return defValue;
+    }
+    Res_value value;
+    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+        if (value.dataType < Res_value::TYPE_FIRST_INT
+                || value.dataType > Res_value::TYPE_LAST_INT) {
+            if (outError != NULL) {
+                *outError = "attribute is not an integer value";
+            }
+            return defValue;
+        }
+    }
+    return value.data;
+}
+
+static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree,
+        uint32_t attrRes, String8* outError, int32_t defValue = -1)
+{
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return defValue;
+    }
+    Res_value value;
+    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+        if (value.dataType == Res_value::TYPE_REFERENCE) {
+            resTable->resolveReference(&value, 0);
+        }
+        if (value.dataType < Res_value::TYPE_FIRST_INT
+                || value.dataType > Res_value::TYPE_LAST_INT) {
+            if (outError != NULL) {
+                *outError = "attribute is not an integer value";
+            }
+            return defValue;
+        }
+    }
+    return value.data;
+}
+
+static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree,
+        uint32_t attrRes, String8* outError)
+{
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        return String8();
+    }
+    Res_value value;
+    if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+        if (value.dataType == Res_value::TYPE_STRING) {
+            size_t len;
+            const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+            return str ? String8(str, len) : String8();
+        }
+        resTable->resolveReference(&value, 0);
+        if (value.dataType != Res_value::TYPE_STRING) {
+            if (outError != NULL) {
+                *outError = "attribute is not a string value";
+            }
+            return String8();
+        }
+    }
+    size_t len;
+    const Res_value* value2 = &value;
+    const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len);
+    return str ? String8(str, len) : String8();
+}
+
+static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable,
+        const ResXMLTree& tree, uint32_t attrRes, String8* outError)
+{
+    ssize_t idx = indexOfAttribute(tree, attrRes);
+    if (idx < 0) {
+        if (outError != NULL) {
+            *outError = "attribute could not be found";
+        }
+        return;
+    }
+    if (tree.getAttributeValue(idx, value) != NO_ERROR) {
+        if (value->dataType == Res_value::TYPE_REFERENCE) {
+            resTable->resolveReference(value, 0);
+        }
+        // The attribute was found and was resolved if need be.
+        return;
+    }
+    if (outError != NULL) {
+        *outError = "error getting resolved resource attribute";
+    }
+}
+
+// These are attribute resource constants for the platform, as found
+// in android.R.attr
+enum {
+    LABEL_ATTR = 0x01010001,
+    ICON_ATTR = 0x01010002,
+    NAME_ATTR = 0x01010003,
+    DEBUGGABLE_ATTR = 0x0101000f,
+    VALUE_ATTR = 0x01010024,
+    VERSION_CODE_ATTR = 0x0101021b,
+    VERSION_NAME_ATTR = 0x0101021c,
+    SCREEN_ORIENTATION_ATTR = 0x0101001e,
+    MIN_SDK_VERSION_ATTR = 0x0101020c,
+    MAX_SDK_VERSION_ATTR = 0x01010271,
+    REQ_TOUCH_SCREEN_ATTR = 0x01010227,
+    REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
+    REQ_HARD_KEYBOARD_ATTR = 0x01010229,
+    REQ_NAVIGATION_ATTR = 0x0101022a,
+    REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
+    TARGET_SDK_VERSION_ATTR = 0x01010270,
+    TEST_ONLY_ATTR = 0x01010272,
+    ANY_DENSITY_ATTR = 0x0101026c,
+    GL_ES_VERSION_ATTR = 0x01010281,
+    SMALL_SCREEN_ATTR = 0x01010284,
+    NORMAL_SCREEN_ATTR = 0x01010285,
+    LARGE_SCREEN_ATTR = 0x01010286,
+    XLARGE_SCREEN_ATTR = 0x010102bf,
+    REQUIRED_ATTR = 0x0101028e,
+    SCREEN_SIZE_ATTR = 0x010102ca,
+    SCREEN_DENSITY_ATTR = 0x010102cb,
+    REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
+    COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
+    LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
+    PUBLIC_KEY_ATTR = 0x010103a6,
+};
+
+const char *getComponentName(String8 &pkgName, String8 &componentName) {
+    ssize_t idx = componentName.find(".");
+    String8 retStr(pkgName);
+    if (idx == 0) {
+        retStr += componentName;
+    } else if (idx < 0) {
+        retStr += ".";
+        retStr += componentName;
+    } else {
+        return componentName.string();
+    }
+    return retStr.string();
+}
+
+static void printCompatibleScreens(ResXMLTree& tree) {
+    size_t len;
+    ResXMLTree::event_code_t code;
+    int depth = 0;
+    bool first = true;
+    printf("compatible-screens:");
+    while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::END_TAG) {
+            depth--;
+            if (depth < 0) {
+                break;
+            }
+            continue;
+        }
+        if (code != ResXMLTree::START_TAG) {
+            continue;
+        }
+        depth++;
+        String8 tag(tree.getElementName(&len));
+        if (tag == "screen") {
+            int32_t screenSize = getIntegerAttribute(tree,
+                    SCREEN_SIZE_ATTR, NULL, -1);
+            int32_t screenDensity = getIntegerAttribute(tree,
+                    SCREEN_DENSITY_ATTR, NULL, -1);
+            if (screenSize > 0 && screenDensity > 0) {
+                if (!first) {
+                    printf(",");
+                }
+                first = false;
+                printf("'%d/%d'", screenSize, screenDensity);
+            }
+        }
+    }
+    printf("\n");
+}
+
+/*
+ * Handle the "dump" command, to extract select data from an archive.
+ */
+extern char CONSOLE_DATA[2925]; // see EOF
+int doDump(Bundle* bundle)
+{
+    status_t result = UNKNOWN_ERROR;
+    Asset* asset = NULL;
+
+    if (bundle->getFileSpecCount() < 1) {
+        fprintf(stderr, "ERROR: no dump option specified\n");
+        return 1;
+    }
+
+    if (bundle->getFileSpecCount() < 2) {
+        fprintf(stderr, "ERROR: no dump file specified\n");
+        return 1;
+    }
+
+    const char* option = bundle->getFileSpecEntry(0);
+    const char* filename = bundle->getFileSpecEntry(1);
+
+    AssetManager assets;
+    void* assetsCookie;
+    if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
+        fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
+        return 1;
+    }
+
+    // Make a dummy config for retrieving resources...  we need to supply
+    // non-default values for some configs so that we can retrieve resources
+    // in the app that don't have a default.  The most important of these is
+    // the API version because key resources like icons will have an implicit
+    // version if they are using newer config types like density.
+    ResTable_config config;
+    config.language[0] = 'e';
+    config.language[1] = 'n';
+    config.country[0] = 'U';
+    config.country[1] = 'S';
+    config.orientation = ResTable_config::ORIENTATION_PORT;
+    config.density = ResTable_config::DENSITY_MEDIUM;
+    config.sdkVersion = 10000; // Very high.
+    config.screenWidthDp = 320;
+    config.screenHeightDp = 480;
+    config.smallestScreenWidthDp = 320;
+    assets.setConfiguration(config);
+
+    const ResTable& res = assets.getResources(false);
+    if (&res == NULL) {
+        fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
+        goto bail;
+    }
+
+    if (strcmp("resources", option) == 0) {
+#ifndef HAVE_ANDROID_OS
+        res.print(bundle->getValues());
+#endif
+
+    } else if (strcmp("strings", option) == 0) {
+        const ResStringPool* pool = res.getTableStringBlock(0);
+        printStringPool(pool);
+
+    } else if (strcmp("xmltree", option) == 0) {
+        if (bundle->getFileSpecCount() < 3) {
+            fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
+            goto bail;
+        }
+
+        for (int i=2; i<bundle->getFileSpecCount(); i++) {
+            const char* resname = bundle->getFileSpecEntry(i);
+            ResXMLTree tree;
+            asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
+            if (asset == NULL) {
+                fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
+                goto bail;
+            }
+
+            if (tree.setTo(asset->getBuffer(true),
+                           asset->getLength()) != NO_ERROR) {
+                fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
+                goto bail;
+            }
+            tree.restart();
+            printXMLBlock(&tree);
+            tree.uninit();
+            delete asset;
+            asset = NULL;
+        }
+
+    } else if (strcmp("xmlstrings", option) == 0) {
+        if (bundle->getFileSpecCount() < 3) {
+            fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
+            goto bail;
+        }
+
+        for (int i=2; i<bundle->getFileSpecCount(); i++) {
+            const char* resname = bundle->getFileSpecEntry(i);
+            ResXMLTree tree;
+            asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
+            if (asset == NULL) {
+                fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
+                goto bail;
+            }
+
+            if (tree.setTo(asset->getBuffer(true),
+                           asset->getLength()) != NO_ERROR) {
+                fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
+                goto bail;
+            }
+            printStringPool(&tree.getStrings());
+            delete asset;
+            asset = NULL;
+        }
+
+    } else {
+        ResXMLTree tree;
+        asset = assets.openNonAsset("AndroidManifest.xml",
+                                            Asset::ACCESS_BUFFER);
+        if (asset == NULL) {
+            fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
+            goto bail;
+        }
+
+        if (tree.setTo(asset->getBuffer(true),
+                       asset->getLength()) != NO_ERROR) {
+            fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
+            goto bail;
+        }
+        tree.restart();
+
+        if (strcmp("permissions", option) == 0) {
+            size_t len;
+            ResXMLTree::event_code_t code;
+            int depth = 0;
+            while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                if (code == ResXMLTree::END_TAG) {
+                    depth--;
+                    continue;
+                }
+                if (code != ResXMLTree::START_TAG) {
+                    continue;
+                }
+                depth++;
+                String8 tag(tree.getElementName(&len));
+                //printf("Depth %d tag %s\n", depth, tag.string());
+                if (depth == 1) {
+                    if (tag != "manifest") {
+                        fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+                        goto bail;
+                    }
+                    String8 pkg = getAttribute(tree, NULL, "package", NULL);
+                    printf("package: %s\n", pkg.string());
+                } else if (depth == 2 && tag == "permission") {
+                    String8 error;
+                    String8 name = getAttribute(tree, NAME_ATTR, &error);
+                    if (error != "") {
+                        fprintf(stderr, "ERROR: %s\n", error.string());
+                        goto bail;
+                    }
+                    printf("permission: %s\n", name.string());
+                } else if (depth == 2 && tag == "uses-permission") {
+                    String8 error;
+                    String8 name = getAttribute(tree, NAME_ATTR, &error);
+                    if (error != "") {
+                        fprintf(stderr, "ERROR: %s\n", error.string());
+                        goto bail;
+                    }
+                    printf("uses-permission: %s\n", name.string());
+                    int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
+                    if (!req) {
+                        printf("optional-permission: %s\n", name.string());
+                    }
+                }
+            }
+        } else if (strcmp("badging", option) == 0) {
+            Vector<String8> locales;
+            res.getLocales(&locales);
+
+            Vector<ResTable_config> configs;
+            res.getConfigurations(&configs);
+            SortedVector<int> densities;
+            const size_t NC = configs.size();
+            for (size_t i=0; i<NC; i++) {
+                int dens = configs[i].density;
+                if (dens == 0) {
+                    dens = 160;
+                }
+                densities.add(dens);
+            }
+
+            size_t len;
+            ResXMLTree::event_code_t code;
+            int depth = 0;
+            String8 error;
+            bool withinActivity = false;
+            bool isMainActivity = false;
+            bool isLauncherActivity = false;
+            bool isSearchable = false;
+            bool withinApplication = false;
+            bool withinReceiver = false;
+            bool withinService = false;
+            bool withinIntentFilter = false;
+            bool hasMainActivity = false;
+            bool hasOtherActivities = false;
+            bool hasOtherReceivers = false;
+            bool hasOtherServices = false;
+            bool hasWallpaperService = false;
+            bool hasImeService = false;
+            bool hasWidgetReceivers = false;
+            bool hasIntentFilter = false;
+            bool actMainActivity = false;
+            bool actWidgetReceivers = false;
+            bool actImeService = false;
+            bool actWallpaperService = false;
+
+            // These two implement the implicit permissions that are granted
+            // to pre-1.6 applications.
+            bool hasWriteExternalStoragePermission = false;
+            bool hasReadPhoneStatePermission = false;
+
+            // If an app requests write storage, they will also get read storage.
+            bool hasReadExternalStoragePermission = false;
+
+            // Implement transition to read and write call log.
+            bool hasReadContactsPermission = false;
+            bool hasWriteContactsPermission = false;
+            bool hasReadCallLogPermission = false;
+            bool hasWriteCallLogPermission = false;
+
+            // This next group of variables is used to implement a group of
+            // backward-compatibility heuristics necessitated by the addition of
+            // some new uses-feature constants in 2.1 and 2.2. In most cases, the
+            // heuristic is "if an app requests a permission but doesn't explicitly
+            // request the corresponding <uses-feature>, presume it's there anyway".
+            bool specCameraFeature = false; // camera-related
+            bool specCameraAutofocusFeature = false;
+            bool reqCameraAutofocusFeature = false;
+            bool reqCameraFlashFeature = false;
+            bool hasCameraPermission = false;
+            bool specLocationFeature = false; // location-related
+            bool specNetworkLocFeature = false;
+            bool reqNetworkLocFeature = false;
+            bool specGpsFeature = false;
+            bool reqGpsFeature = false;
+            bool hasMockLocPermission = false;
+            bool hasCoarseLocPermission = false;
+            bool hasGpsPermission = false;
+            bool hasGeneralLocPermission = false;
+            bool specBluetoothFeature = false; // Bluetooth API-related
+            bool hasBluetoothPermission = false;
+            bool specMicrophoneFeature = false; // microphone-related
+            bool hasRecordAudioPermission = false;
+            bool specWiFiFeature = false;
+            bool hasWiFiPermission = false;
+            bool specTelephonyFeature = false; // telephony-related
+            bool reqTelephonySubFeature = false;
+            bool hasTelephonyPermission = false;
+            bool specTouchscreenFeature = false; // touchscreen-related
+            bool specMultitouchFeature = false;
+            bool reqDistinctMultitouchFeature = false;
+            bool specScreenPortraitFeature = false;
+            bool specScreenLandscapeFeature = false;
+            bool reqScreenPortraitFeature = false;
+            bool reqScreenLandscapeFeature = false;
+            // 2.2 also added some other features that apps can request, but that
+            // have no corresponding permission, so we cannot implement any
+            // back-compatibility heuristic for them. The below are thus unnecessary
+            // (but are retained here for documentary purposes.)
+            //bool specCompassFeature = false;
+            //bool specAccelerometerFeature = false;
+            //bool specProximityFeature = false;
+            //bool specAmbientLightFeature = false;
+            //bool specLiveWallpaperFeature = false;
+
+            int targetSdk = 0;
+            int smallScreen = 1;
+            int normalScreen = 1;
+            int largeScreen = 1;
+            int xlargeScreen = 1;
+            int anyDensity = 1;
+            int requiresSmallestWidthDp = 0;
+            int compatibleWidthLimitDp = 0;
+            int largestWidthLimitDp = 0;
+            String8 pkg;
+            String8 activityName;
+            String8 activityLabel;
+            String8 activityIcon;
+            String8 receiverName;
+            String8 serviceName;
+            while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                if (code == ResXMLTree::END_TAG) {
+                    depth--;
+                    if (depth < 2) {
+                        withinApplication = false;
+                    } else if (depth < 3) {
+                        if (withinActivity && isMainActivity && isLauncherActivity) {
+                            const char *aName = getComponentName(pkg, activityName);
+                            printf("launchable-activity:");
+                            if (aName != NULL) {
+                                printf(" name='%s' ", aName);
+                            }
+                            printf(" label='%s' icon='%s'\n",
+                                    activityLabel.string(),
+                                    activityIcon.string());
+                        }
+                        if (!hasIntentFilter) {
+                            hasOtherActivities |= withinActivity;
+                            hasOtherReceivers |= withinReceiver;
+                            hasOtherServices |= withinService;
+                        }
+                        withinActivity = false;
+                        withinService = false;
+                        withinReceiver = false;
+                        hasIntentFilter = false;
+                        isMainActivity = isLauncherActivity = false;
+                    } else if (depth < 4) {
+                        if (withinIntentFilter) {
+                            if (withinActivity) {
+                                hasMainActivity |= actMainActivity;
+                                hasOtherActivities |= !actMainActivity;
+                            } else if (withinReceiver) {
+                                hasWidgetReceivers |= actWidgetReceivers;
+                                hasOtherReceivers |= !actWidgetReceivers;
+                            } else if (withinService) {
+                                hasImeService |= actImeService;
+                                hasWallpaperService |= actWallpaperService;
+                                hasOtherServices |= (!actImeService && !actWallpaperService);
+                            }
+                        }
+                        withinIntentFilter = false;
+                    }
+                    continue;
+                }
+                if (code != ResXMLTree::START_TAG) {
+                    continue;
+                }
+                depth++;
+                String8 tag(tree.getElementName(&len));
+                //printf("Depth %d,  %s\n", depth, tag.string());
+                if (depth == 1) {
+                    if (tag != "manifest") {
+                        fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+                        goto bail;
+                    }
+                    pkg = getAttribute(tree, NULL, "package", NULL);
+                    printf("package: name='%s' ", pkg.string());
+                    int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
+                    if (error != "") {
+                        fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
+                        goto bail;
+                    }
+                    if (versionCode > 0) {
+                        printf("versionCode='%d' ", versionCode);
+                    } else {
+                        printf("versionCode='' ");
+                    }
+                    String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
+                    if (error != "") {
+                        fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
+                        goto bail;
+                    }
+                    printf("versionName='%s'\n", versionName.string());
+                } else if (depth == 2) {
+                    withinApplication = false;
+                    if (tag == "application") {
+                        withinApplication = true;
+
+                        String8 label;
+                        const size_t NL = locales.size();
+                        for (size_t i=0; i<NL; i++) {
+                            const char* localeStr =  locales[i].string();
+                            assets.setLocale(localeStr != NULL ? localeStr : "");
+                            String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+                            if (llabel != "") {
+                                if (localeStr == NULL || strlen(localeStr) == 0) {
+                                    label = llabel;
+                                    printf("application-label:'%s'\n", llabel.string());
+                                } else {
+                                    if (label == "") {
+                                        label = llabel;
+                                    }
+                                    printf("application-label-%s:'%s'\n", localeStr,
+                                            llabel.string());
+                                }
+                            }
+                        }
+
+                        ResTable_config tmpConfig = config;
+                        const size_t ND = densities.size();
+                        for (size_t i=0; i<ND; i++) {
+                            tmpConfig.density = densities[i];
+                            assets.setConfiguration(tmpConfig);
+                            String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+                            if (icon != "") {
+                                printf("application-icon-%d:'%s'\n", densities[i], icon.string());
+                            }
+                        }
+                        assets.setConfiguration(config);
+
+                        String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+                        int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+                        printf("application: label='%s' ", label.string());
+                        printf("icon='%s'\n", icon.string());
+                        if (testOnly != 0) {
+                            printf("testOnly='%d'\n", testOnly);
+                        }
+
+                        int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+                        if (debuggable != 0) {
+                            printf("application-debuggable\n");
+                        }
+                    } else if (tag == "uses-sdk") {
+                        int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
+                        if (error != "") {
+                            error = "";
+                            String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
+                                        error.string());
+                                goto bail;
+                            }
+                            if (name == "Donut") targetSdk = 4;
+                            printf("sdkVersion:'%s'\n", name.string());
+                        } else if (code != -1) {
+                            targetSdk = code;
+                            printf("sdkVersion:'%d'\n", code);
+                        }
+                        code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
+                        if (code != -1) {
+                            printf("maxSdkVersion:'%d'\n", code);
+                        }
+                        code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
+                        if (error != "") {
+                            error = "";
+                            String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
+                                        error.string());
+                                goto bail;
+                            }
+                            if (name == "Donut" && targetSdk < 4) targetSdk = 4;
+                            printf("targetSdkVersion:'%s'\n", name.string());
+                        } else if (code != -1) {
+                            if (targetSdk < code) {
+                                targetSdk = code;
+                            }
+                            printf("targetSdkVersion:'%d'\n", code);
+                        }
+                    } else if (tag == "uses-configuration") {
+                        int32_t reqTouchScreen = getIntegerAttribute(tree,
+                                REQ_TOUCH_SCREEN_ATTR, NULL, 0);
+                        int32_t reqKeyboardType = getIntegerAttribute(tree,
+                                REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
+                        int32_t reqHardKeyboard = getIntegerAttribute(tree,
+                                REQ_HARD_KEYBOARD_ATTR, NULL, 0);
+                        int32_t reqNavigation = getIntegerAttribute(tree,
+                                REQ_NAVIGATION_ATTR, NULL, 0);
+                        int32_t reqFiveWayNav = getIntegerAttribute(tree,
+                                REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
+                        printf("uses-configuration:");
+                        if (reqTouchScreen != 0) {
+                            printf(" reqTouchScreen='%d'", reqTouchScreen);
+                        }
+                        if (reqKeyboardType != 0) {
+                            printf(" reqKeyboardType='%d'", reqKeyboardType);
+                        }
+                        if (reqHardKeyboard != 0) {
+                            printf(" reqHardKeyboard='%d'", reqHardKeyboard);
+                        }
+                        if (reqNavigation != 0) {
+                            printf(" reqNavigation='%d'", reqNavigation);
+                        }
+                        if (reqFiveWayNav != 0) {
+                            printf(" reqFiveWayNav='%d'", reqFiveWayNav);
+                        }
+                        printf("\n");
+                    } else if (tag == "supports-screens") {
+                        smallScreen = getIntegerAttribute(tree,
+                                SMALL_SCREEN_ATTR, NULL, 1);
+                        normalScreen = getIntegerAttribute(tree,
+                                NORMAL_SCREEN_ATTR, NULL, 1);
+                        largeScreen = getIntegerAttribute(tree,
+                                LARGE_SCREEN_ATTR, NULL, 1);
+                        xlargeScreen = getIntegerAttribute(tree,
+                                XLARGE_SCREEN_ATTR, NULL, 1);
+                        anyDensity = getIntegerAttribute(tree,
+                                ANY_DENSITY_ATTR, NULL, 1);
+                        requiresSmallestWidthDp = getIntegerAttribute(tree,
+                                REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
+                        compatibleWidthLimitDp = getIntegerAttribute(tree,
+                                COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
+                        largestWidthLimitDp = getIntegerAttribute(tree,
+                                LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
+                    } else if (tag == "uses-feature") {
+                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+
+                        if (name != "" && error == "") {
+                            int req = getIntegerAttribute(tree,
+                                    REQUIRED_ATTR, NULL, 1);
+
+                            if (name == "android.hardware.camera") {
+                                specCameraFeature = true;
+                            } else if (name == "android.hardware.camera.autofocus") {
+                                // these have no corresponding permission to check for,
+                                // but should imply the foundational camera permission
+                                reqCameraAutofocusFeature = reqCameraAutofocusFeature || req;
+                                specCameraAutofocusFeature = true;
+                            } else if (req && (name == "android.hardware.camera.flash")) {
+                                // these have no corresponding permission to check for,
+                                // but should imply the foundational camera permission
+                                reqCameraFlashFeature = true;
+                            } else if (name == "android.hardware.location") {
+                                specLocationFeature = true;
+                            } else if (name == "android.hardware.location.network") {
+                                specNetworkLocFeature = true;
+                                reqNetworkLocFeature = reqNetworkLocFeature || req;
+                            } else if (name == "android.hardware.location.gps") {
+                                specGpsFeature = true;
+                                reqGpsFeature = reqGpsFeature || req;
+                            } else if (name == "android.hardware.bluetooth") {
+                                specBluetoothFeature = true;
+                            } else if (name == "android.hardware.touchscreen") {
+                                specTouchscreenFeature = true;
+                            } else if (name == "android.hardware.touchscreen.multitouch") {
+                                specMultitouchFeature = true;
+                            } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
+                                reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req;
+                            } else if (name == "android.hardware.microphone") {
+                                specMicrophoneFeature = true;
+                            } else if (name == "android.hardware.wifi") {
+                                specWiFiFeature = true;
+                            } else if (name == "android.hardware.telephony") {
+                                specTelephonyFeature = true;
+                            } else if (req && (name == "android.hardware.telephony.gsm" ||
+                                               name == "android.hardware.telephony.cdma")) {
+                                // these have no corresponding permission to check for,
+                                // but should imply the foundational telephony permission
+                                reqTelephonySubFeature = true;
+                            } else if (name == "android.hardware.screen.portrait") {
+                                specScreenPortraitFeature = true;
+                            } else if (name == "android.hardware.screen.landscape") {
+                                specScreenLandscapeFeature = true;
+                            }
+                            printf("uses-feature%s:'%s'\n",
+                                    req ? "" : "-not-required", name.string());
+                        } else {
+                            int vers = getIntegerAttribute(tree,
+                                    GL_ES_VERSION_ATTR, &error);
+                            if (error == "") {
+                                printf("uses-gl-es:'0x%x'\n", vers);
+                            }
+                        }
+                    } else if (tag == "uses-permission") {
+                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        if (name != "" && error == "") {
+                            if (name == "android.permission.CAMERA") {
+                                hasCameraPermission = true;
+                            } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
+                                hasGpsPermission = true;
+                            } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
+                                hasMockLocPermission = true;
+                            } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
+                                hasCoarseLocPermission = true;
+                            } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
+                                       name == "android.permission.INSTALL_LOCATION_PROVIDER") {
+                                hasGeneralLocPermission = true;
+                            } else if (name == "android.permission.BLUETOOTH" ||
+                                       name == "android.permission.BLUETOOTH_ADMIN") {
+                                hasBluetoothPermission = true;
+                            } else if (name == "android.permission.RECORD_AUDIO") {
+                                hasRecordAudioPermission = true;
+                            } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
+                                       name == "android.permission.CHANGE_WIFI_STATE" ||
+                                       name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
+                                hasWiFiPermission = true;
+                            } else if (name == "android.permission.CALL_PHONE" ||
+                                       name == "android.permission.CALL_PRIVILEGED" ||
+                                       name == "android.permission.MODIFY_PHONE_STATE" ||
+                                       name == "android.permission.PROCESS_OUTGOING_CALLS" ||
+                                       name == "android.permission.READ_SMS" ||
+                                       name == "android.permission.RECEIVE_SMS" ||
+                                       name == "android.permission.RECEIVE_MMS" ||
+                                       name == "android.permission.RECEIVE_WAP_PUSH" ||
+                                       name == "android.permission.SEND_SMS" ||
+                                       name == "android.permission.WRITE_APN_SETTINGS" ||
+                                       name == "android.permission.WRITE_SMS") {
+                                hasTelephonyPermission = true;
+                            } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
+                                hasWriteExternalStoragePermission = true;
+                            } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
+                                hasReadExternalStoragePermission = true;
+                            } else if (name == "android.permission.READ_PHONE_STATE") {
+                                hasReadPhoneStatePermission = true;
+                            } else if (name == "android.permission.READ_CONTACTS") {
+                                hasReadContactsPermission = true;
+                            } else if (name == "android.permission.WRITE_CONTACTS") {
+                                hasWriteContactsPermission = true;
+                            } else if (name == "android.permission.READ_CALL_LOG") {
+                                hasReadCallLogPermission = true;
+                            } else if (name == "android.permission.WRITE_CALL_LOG") {
+                                hasWriteCallLogPermission = true;
+                            }
+                            printf("uses-permission:'%s'\n", name.string());
+                            int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
+                            if (!req) {
+                                printf("optional-permission:'%s'\n", name.string());
+                            }
+                        } else {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+                                    error.string());
+                            goto bail;
+                        }
+                    } else if (tag == "uses-package") {
+                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        if (name != "" && error == "") {
+                            printf("uses-package:'%s'\n", name.string());
+                        } else {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+                                    error.string());
+                                goto bail;
+                        }
+                    } else if (tag == "original-package") {
+                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        if (name != "" && error == "") {
+                            printf("original-package:'%s'\n", name.string());
+                        } else {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+                                    error.string());
+                                goto bail;
+                        }
+                    } else if (tag == "supports-gl-texture") {
+                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        if (name != "" && error == "") {
+                            printf("supports-gl-texture:'%s'\n", name.string());
+                        } else {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+                                    error.string());
+                                goto bail;
+                        }
+                    } else if (tag == "compatible-screens") {
+                        printCompatibleScreens(tree);
+                        depth--;
+                    } else if (tag == "package-verifier") {
+                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        if (name != "" && error == "") {
+                            String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
+                            if (publicKey != "" && error == "") {
+                                printf("package-verifier: name='%s' publicKey='%s'\n",
+                                        name.string(), publicKey.string());
+                            }
+                        }
+                    }
+                } else if (depth == 3 && withinApplication) {
+                    withinActivity = false;
+                    withinReceiver = false;
+                    withinService = false;
+                    hasIntentFilter = false;
+                    if(tag == "activity") {
+                        withinActivity = true;
+                        activityName = getAttribute(tree, NAME_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+
+                        activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+
+                        activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+
+                        int32_t orien = getResolvedIntegerAttribute(&res, tree,
+                                SCREEN_ORIENTATION_ATTR, &error);
+                        if (error == "") {
+                            if (orien == 0 || orien == 6 || orien == 8) {
+                                // Requests landscape, sensorLandscape, or reverseLandscape.
+                                reqScreenLandscapeFeature = true;
+                            } else if (orien == 1 || orien == 7 || orien == 9) {
+                                // Requests portrait, sensorPortrait, or reversePortrait.
+                                reqScreenPortraitFeature = true;
+                            }
+                        }
+                    } else if (tag == "uses-library") {
+                        String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute for uses-library: %s\n", error.string());
+                            goto bail;
+                        }
+                        int req = getIntegerAttribute(tree,
+                                REQUIRED_ATTR, NULL, 1);
+                        printf("uses-library%s:'%s'\n",
+                                req ? "" : "-not-required", libraryName.string());
+                    } else if (tag == "receiver") {
+                        withinReceiver = true;
+                        receiverName = getAttribute(tree, NAME_ATTR, &error);
+
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute for "
+                                    "receiver:%s\n", error.string());
+                            goto bail;
+                        }
+                    } else if (tag == "service") {
+                        withinService = true;
+                        serviceName = getAttribute(tree, NAME_ATTR, &error);
+
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute for "
+                                    "service:%s\n", error.string());
+                            goto bail;
+                        }
+                    } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
+                        String8 metaDataName = getAttribute(tree, NAME_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute for "
+                                    "meta-data:%s\n", error.string());
+                            goto bail;
+                        }
+                        printf("meta-data: name='%s' ", metaDataName.string());
+                        Res_value value;
+                        getResolvedResourceAttribute(&value, &res, tree, VALUE_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:value' attribute for "
+                                    "meta-data:%s\n", error.string());
+                            goto bail;
+                        }
+                        if (value.dataType == Res_value::TYPE_STRING) {
+                            String8 metaDataValue = getAttribute(tree, value.data, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting 'android:value' attribute for "
+                                        "meta-data: %s\n", error.string());
+                                goto bail;
+                            }
+                            printf("value='%s'\n", metaDataValue.string());
+                        } else if (Res_value::TYPE_FIRST_INT <= value.dataType &&
+                                value.dataType <= Res_value::TYPE_LAST_INT) {
+                            printf("value='%d'\n", value.data);
+                        } else {
+                            printf("value=(type 0x%x)0x%x", (int)value.dataType, (int)value.data);
+                        }
+                    }
+                } else if ((depth == 4) && (tag == "intent-filter")) {
+                    hasIntentFilter = true;
+                    withinIntentFilter = true;
+                    actMainActivity = actWidgetReceivers = actImeService = actWallpaperService =
+                            false;
+                } else if ((depth == 5) && withinIntentFilter) {
+                    String8 action;
+                    if (tag == "action") {
+                        action = getAttribute(tree, NAME_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+                                    error.string());
+                            goto bail;
+                        }
+                        if (withinActivity) {
+                            if (action == "android.intent.action.MAIN") {
+                                isMainActivity = true;
+                                actMainActivity = true;
+                            }
+                        } else if (withinReceiver) {
+                            if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
+                                actWidgetReceivers = true;
+                            }
+                        } else if (withinService) {
+                            if (action == "android.view.InputMethod") {
+                                actImeService = true;
+                            } else if (action == "android.service.wallpaper.WallpaperService") {
+                                actWallpaperService = true;
+                            }
+                        }
+                        if (action == "android.intent.action.SEARCH") {
+                            isSearchable = true;
+                        }
+                    }
+
+                    if (tag == "category") {
+                        String8 category = getAttribute(tree, NAME_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string());
+                            goto bail;
+                        }
+                        if (withinActivity) {
+                            if (category == "android.intent.category.LAUNCHER") {
+                                isLauncherActivity = true;
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Pre-1.6 implicitly granted permission compatibility logic
+            if (targetSdk < 4) {
+                if (!hasWriteExternalStoragePermission) {
+                    printf("uses-permission:'android.permission.WRITE_EXTERNAL_STORAGE'\n");
+                    printf("uses-implied-permission:'android.permission.WRITE_EXTERNAL_STORAGE'," \
+                            "'targetSdkVersion < 4'\n");
+                    hasWriteExternalStoragePermission = true;
+                }
+                if (!hasReadPhoneStatePermission) {
+                    printf("uses-permission:'android.permission.READ_PHONE_STATE'\n");
+                    printf("uses-implied-permission:'android.permission.READ_PHONE_STATE'," \
+                            "'targetSdkVersion < 4'\n");
+                }
+            }
+
+            // If the application has requested WRITE_EXTERNAL_STORAGE, we will
+            // force them to always take READ_EXTERNAL_STORAGE as well.  We always
+            // do this (regardless of target API version) because we can't have
+            // an app with write permission but not read permission.
+            if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
+                printf("uses-permission:'android.permission.READ_EXTERNAL_STORAGE'\n");
+                printf("uses-implied-permission:'android.permission.READ_EXTERNAL_STORAGE'," \
+                        "'requested WRITE_EXTERNAL_STORAGE'\n");
+            }
+
+            // Pre-JellyBean call log permission compatibility.
+            if (targetSdk < 16) {
+                if (!hasReadCallLogPermission && hasReadContactsPermission) {
+                    printf("uses-permission:'android.permission.READ_CALL_LOG'\n");
+                    printf("uses-implied-permission:'android.permission.READ_CALL_LOG'," \
+                            "'targetSdkVersion < 16 and requested READ_CONTACTS'\n");
+                }
+                if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
+                    printf("uses-permission:'android.permission.WRITE_CALL_LOG'\n");
+                    printf("uses-implied-permission:'android.permission.WRITE_CALL_LOG'," \
+                            "'targetSdkVersion < 16 and requested WRITE_CONTACTS'\n");
+                }
+            }
+
+            /* The following blocks handle printing "inferred" uses-features, based
+             * on whether related features or permissions are used by the app.
+             * Note that the various spec*Feature variables denote whether the
+             * relevant tag was *present* in the AndroidManfest, not that it was
+             * present and set to true.
+             */
+            // Camera-related back-compatibility logic
+            if (!specCameraFeature) {
+                if (reqCameraFlashFeature) {
+                    // if app requested a sub-feature (autofocus or flash) and didn't
+                    // request the base camera feature, we infer that it meant to
+                    printf("uses-feature:'android.hardware.camera'\n");
+                    printf("uses-implied-feature:'android.hardware.camera'," \
+                            "'requested android.hardware.camera.flash feature'\n");
+                } else if (reqCameraAutofocusFeature) {
+                    // if app requested a sub-feature (autofocus or flash) and didn't
+                    // request the base camera feature, we infer that it meant to
+                    printf("uses-feature:'android.hardware.camera'\n");
+                    printf("uses-implied-feature:'android.hardware.camera'," \
+                            "'requested android.hardware.camera.autofocus feature'\n");
+                } else if (hasCameraPermission) {
+                    // if app wants to use camera but didn't request the feature, we infer 
+                    // that it meant to, and further that it wants autofocus
+                    // (which was the 1.0 - 1.5 behavior)
+                    printf("uses-feature:'android.hardware.camera'\n");
+                    if (!specCameraAutofocusFeature) {
+                        printf("uses-feature:'android.hardware.camera.autofocus'\n");
+                        printf("uses-implied-feature:'android.hardware.camera.autofocus'," \
+                                "'requested android.permission.CAMERA permission'\n");
+                    }
+                }
+            }
+
+            // Location-related back-compatibility logic
+            if (!specLocationFeature &&
+                (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission ||
+                 hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) {
+                // if app either takes a location-related permission or requests one of the
+                // sub-features, we infer that it also meant to request the base location feature
+                printf("uses-feature:'android.hardware.location'\n");
+                printf("uses-implied-feature:'android.hardware.location'," \
+                        "'requested a location access permission'\n");
+            }
+            if (!specGpsFeature && hasGpsPermission) {
+                // if app takes GPS (FINE location) perm but does not request the GPS
+                // feature, we infer that it meant to
+                printf("uses-feature:'android.hardware.location.gps'\n");
+                printf("uses-implied-feature:'android.hardware.location.gps'," \
+                        "'requested android.permission.ACCESS_FINE_LOCATION permission'\n");
+            }
+            if (!specNetworkLocFeature && hasCoarseLocPermission) {
+                // if app takes Network location (COARSE location) perm but does not request the
+                // network location feature, we infer that it meant to
+                printf("uses-feature:'android.hardware.location.network'\n");
+                printf("uses-implied-feature:'android.hardware.location.network'," \
+                        "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n");
+            }
+
+            // Bluetooth-related compatibility logic
+            if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) {
+                // if app takes a Bluetooth permission but does not request the Bluetooth
+                // feature, we infer that it meant to
+                printf("uses-feature:'android.hardware.bluetooth'\n");
+                printf("uses-implied-feature:'android.hardware.bluetooth'," \
+                        "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \
+                        "permission and targetSdkVersion > 4'\n");
+            }
+
+            // Microphone-related compatibility logic
+            if (!specMicrophoneFeature && hasRecordAudioPermission) {
+                // if app takes the record-audio permission but does not request the microphone
+                // feature, we infer that it meant to
+                printf("uses-feature:'android.hardware.microphone'\n");
+                printf("uses-implied-feature:'android.hardware.microphone'," \
+                        "'requested android.permission.RECORD_AUDIO permission'\n");
+            }
+
+            // WiFi-related compatibility logic
+            if (!specWiFiFeature && hasWiFiPermission) {
+                // if app takes one of the WiFi permissions but does not request the WiFi
+                // feature, we infer that it meant to
+                printf("uses-feature:'android.hardware.wifi'\n");
+                printf("uses-implied-feature:'android.hardware.wifi'," \
+                        "'requested android.permission.ACCESS_WIFI_STATE, " \
+                        "android.permission.CHANGE_WIFI_STATE, or " \
+                        "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n");
+            }
+
+            // Telephony-related compatibility logic
+            if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) {
+                // if app takes one of the telephony permissions or requests a sub-feature but
+                // does not request the base telephony feature, we infer that it meant to
+                printf("uses-feature:'android.hardware.telephony'\n");
+                printf("uses-implied-feature:'android.hardware.telephony'," \
+                        "'requested a telephony-related permission or feature'\n");
+            }
+
+            // Touchscreen-related back-compatibility logic
+            if (!specTouchscreenFeature) { // not a typo!
+                // all apps are presumed to require a touchscreen, unless they explicitly say
+                // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
+                // Note that specTouchscreenFeature is true if the tag is present, regardless
+                // of whether its value is true or false, so this is safe
+                printf("uses-feature:'android.hardware.touchscreen'\n");
+                printf("uses-implied-feature:'android.hardware.touchscreen'," \
+                        "'assumed you require a touch screen unless explicitly made optional'\n");
+            }
+            if (!specMultitouchFeature && reqDistinctMultitouchFeature) {
+                // if app takes one of the telephony permissions or requests a sub-feature but
+                // does not request the base telephony feature, we infer that it meant to
+                printf("uses-feature:'android.hardware.touchscreen.multitouch'\n");
+                printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \
+                        "'requested android.hardware.touchscreen.multitouch.distinct feature'\n");
+            }
+
+            // Landscape/portrait-related compatibility logic
+            if (!specScreenLandscapeFeature && !specScreenPortraitFeature) {
+                // If the app has specified any activities in its manifest
+                // that request a specific orientation, then assume that
+                // orientation is required.
+                if (reqScreenLandscapeFeature) {
+                    printf("uses-feature:'android.hardware.screen.landscape'\n");
+                    printf("uses-implied-feature:'android.hardware.screen.landscape'," \
+                            "'one or more activities have specified a landscape orientation'\n");
+                }
+                if (reqScreenPortraitFeature) {
+                    printf("uses-feature:'android.hardware.screen.portrait'\n");
+                    printf("uses-implied-feature:'android.hardware.screen.portrait'," \
+                            "'one or more activities have specified a portrait orientation'\n");
+                }
+            }
+
+            if (hasMainActivity) {
+                printf("main\n");
+            }
+            if (hasWidgetReceivers) {
+                printf("app-widget\n");
+            }
+            if (hasImeService) {
+                printf("ime\n");
+            }
+            if (hasWallpaperService) {
+                printf("wallpaper\n");
+            }
+            if (hasOtherActivities) {
+                printf("other-activities\n");
+            }
+            if (isSearchable) {
+                printf("search\n");
+            }
+            if (hasOtherReceivers) {
+                printf("other-receivers\n");
+            }
+            if (hasOtherServices) {
+                printf("other-services\n");
+            }
+
+            // For modern apps, if screen size buckets haven't been specified
+            // but the new width ranges have, then infer the buckets from them.
+            if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
+                    && requiresSmallestWidthDp > 0) {
+                int compatWidth = compatibleWidthLimitDp;
+                if (compatWidth <= 0) {
+                    compatWidth = requiresSmallestWidthDp;
+                }
+                if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
+                    smallScreen = -1;
+                } else {
+                    smallScreen = 0;
+                }
+                if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
+                    normalScreen = -1;
+                } else {
+                    normalScreen = 0;
+                }
+                if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
+                    largeScreen = -1;
+                } else {
+                    largeScreen = 0;
+                }
+                if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
+                    xlargeScreen = -1;
+                } else {
+                    xlargeScreen = 0;
+                }
+            }
+
+            // Determine default values for any unspecified screen sizes,
+            // based on the target SDK of the package.  As of 4 (donut)
+            // the screen size support was introduced, so all default to
+            // enabled.
+            if (smallScreen > 0) {
+                smallScreen = targetSdk >= 4 ? -1 : 0;
+            }
+            if (normalScreen > 0) {
+                normalScreen = -1;
+            }
+            if (largeScreen > 0) {
+                largeScreen = targetSdk >= 4 ? -1 : 0;
+            }
+            if (xlargeScreen > 0) {
+                // Introduced in Gingerbread.
+                xlargeScreen = targetSdk >= 9 ? -1 : 0;
+            }
+            if (anyDensity > 0) {
+                anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
+                        || compatibleWidthLimitDp > 0) ? -1 : 0;
+            }
+            printf("supports-screens:");
+            if (smallScreen != 0) {
+                printf(" 'small'");
+            }
+            if (normalScreen != 0) {
+                printf(" 'normal'");
+            }
+            if (largeScreen != 0) {
+                printf(" 'large'");
+            }
+            if (xlargeScreen != 0) {
+                printf(" 'xlarge'");
+            }
+            printf("\n");
+            printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
+            if (requiresSmallestWidthDp > 0) {
+                printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
+            }
+            if (compatibleWidthLimitDp > 0) {
+                printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
+            }
+            if (largestWidthLimitDp > 0) {
+                printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
+            }
+
+            printf("locales:");
+            const size_t NL = locales.size();
+            for (size_t i=0; i<NL; i++) {
+                const char* localeStr =  locales[i].string();
+                if (localeStr == NULL || strlen(localeStr) == 0) {
+                    localeStr = "--_--";
+                }
+                printf(" '%s'", localeStr);
+            }
+            printf("\n");
+
+            printf("densities:");
+            const size_t ND = densities.size();
+            for (size_t i=0; i<ND; i++) {
+                printf(" '%d'", densities[i]);
+            }
+            printf("\n");
+
+            AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
+            if (dir != NULL) {
+                if (dir->getFileCount() > 0) {
+                    printf("native-code:");
+                    for (size_t i=0; i<dir->getFileCount(); i++) {
+                        printf(" '%s'", dir->getFileName(i).string());
+                    }
+                    printf("\n");
+                }
+                delete dir;
+            }
+        } else if (strcmp("badger", option) == 0) {
+            printf("%s", CONSOLE_DATA);
+        } else if (strcmp("configurations", option) == 0) {
+            Vector<ResTable_config> configs;
+            res.getConfigurations(&configs);
+            const size_t N = configs.size();
+            for (size_t i=0; i<N; i++) {
+                printf("%s\n", configs[i].toString().string());
+            }
+        } else {
+            fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
+            goto bail;
+        }
+    }
+
+    result = NO_ERROR;
+
+bail:
+    if (asset) {
+        delete asset;
+    }
+    return (result != NO_ERROR);
+}
+
+
+/*
+ * Handle the "add" command, which wants to add files to a new or
+ * pre-existing archive.
+ */
+int doAdd(Bundle* bundle)
+{
+    ZipFile* zip = NULL;
+    status_t result = UNKNOWN_ERROR;
+    const char* zipFileName;
+
+    if (bundle->getUpdate()) {
+        /* avoid confusion */
+        fprintf(stderr, "ERROR: can't use '-u' with add\n");
+        goto bail;
+    }
+
+    if (bundle->getFileSpecCount() < 1) {
+        fprintf(stderr, "ERROR: must specify zip file name\n");
+        goto bail;
+    }
+    zipFileName = bundle->getFileSpecEntry(0);
+
+    if (bundle->getFileSpecCount() < 2) {
+        fprintf(stderr, "NOTE: nothing to do\n");
+        goto bail;
+    }
+
+    zip = openReadWrite(zipFileName, true);
+    if (zip == NULL) {
+        fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
+        goto bail;
+    }
+
+    for (int i = 1; i < bundle->getFileSpecCount(); i++) {
+        const char* fileName = bundle->getFileSpecEntry(i);
+
+        if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
+            printf(" '%s'... (from gzip)\n", fileName);
+            result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
+        } else {
+            if (bundle->getJunkPath()) {
+                String8 storageName = String8(fileName).getPathLeaf();
+                printf(" '%s' as '%s'...\n", fileName, storageName.string());
+                result = zip->add(fileName, storageName.string(),
+                                  bundle->getCompressionMethod(), NULL);
+            } else {
+                printf(" '%s'...\n", fileName);
+                result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
+            }
+        }
+        if (result != NO_ERROR) {
+            fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
+            if (result == NAME_NOT_FOUND) {
+                fprintf(stderr, ": file not found\n");
+            } else if (result == ALREADY_EXISTS) {
+                fprintf(stderr, ": already exists in archive\n");
+            } else {
+                fprintf(stderr, "\n");
+            }
+            goto bail;
+        }
+    }
+
+    result = NO_ERROR;
+
+bail:
+    delete zip;
+    return (result != NO_ERROR);
+}
+
+
+/*
+ * Delete files from an existing archive.
+ */
+int doRemove(Bundle* bundle)
+{
+    ZipFile* zip = NULL;
+    status_t result = UNKNOWN_ERROR;
+    const char* zipFileName;
+
+    if (bundle->getFileSpecCount() < 1) {
+        fprintf(stderr, "ERROR: must specify zip file name\n");
+        goto bail;
+    }
+    zipFileName = bundle->getFileSpecEntry(0);
+
+    if (bundle->getFileSpecCount() < 2) {
+        fprintf(stderr, "NOTE: nothing to do\n");
+        goto bail;
+    }
+
+    zip = openReadWrite(zipFileName, false);
+    if (zip == NULL) {
+        fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
+            zipFileName);
+        goto bail;
+    }
+
+    for (int i = 1; i < bundle->getFileSpecCount(); i++) {
+        const char* fileName = bundle->getFileSpecEntry(i);
+        ZipEntry* entry;
+
+        entry = zip->getEntryByName(fileName);
+        if (entry == NULL) {
+            printf(" '%s' NOT FOUND\n", fileName);
+            continue;
+        }
+
+        result = zip->remove(entry);
+
+        if (result != NO_ERROR) {
+            fprintf(stderr, "Unable to delete '%s' from '%s'\n",
+                bundle->getFileSpecEntry(i), zipFileName);
+            goto bail;
+        }
+    }
+
+    /* update the archive */
+    zip->flush();
+
+bail:
+    delete zip;
+    return (result != NO_ERROR);
+}
+
+
+/*
+ * Package up an asset directory and associated application files.
+ */
+int doPackage(Bundle* bundle)
+{
+    const char* outputAPKFile;
+    int retVal = 1;
+    status_t err;
+    sp<AaptAssets> assets;
+    int N;
+    FILE* fp;
+    String8 dependencyFile;
+
+    // -c zz_ZZ means do pseudolocalization
+    ResourceFilter filter;
+    err = filter.parse(bundle->getConfigurations());
+    if (err != NO_ERROR) {
+        goto bail;
+    }
+    if (filter.containsPseudo()) {
+        bundle->setPseudolocalize(true);
+    }
+
+    N = bundle->getFileSpecCount();
+    if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
+            && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) {
+        fprintf(stderr, "ERROR: no input files\n");
+        goto bail;
+    }
+
+    outputAPKFile = bundle->getOutputAPKFile();
+
+    // Make sure the filenames provided exist and are of the appropriate type.
+    if (outputAPKFile) {
+        FileType type;
+        type = getFileType(outputAPKFile);
+        if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
+            fprintf(stderr,
+                "ERROR: output file '%s' exists but is not regular file\n",
+                outputAPKFile);
+            goto bail;
+        }
+    }
+
+    // Load the assets.
+    assets = new AaptAssets();
+
+    // Set up the resource gathering in assets if we're going to generate
+    // dependency files. Every time we encounter a resource while slurping
+    // the tree, we'll add it to these stores so we have full resource paths
+    // to write to a dependency file.
+    if (bundle->getGenDependencies()) {
+        sp<FilePathStore> resPathStore = new FilePathStore;
+        assets->setFullResPaths(resPathStore);
+        sp<FilePathStore> assetPathStore = new FilePathStore;
+        assets->setFullAssetPaths(assetPathStore);
+    }
+
+    err = assets->slurpFromArgs(bundle);
+    if (err < 0) {
+        goto bail;
+    }
+
+    if (bundle->getVerbose()) {
+        assets->print(String8());
+    }
+
+    // If they asked for any fileAs that need to be compiled, do so.
+    if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
+        err = buildResources(bundle, assets);
+        if (err != 0) {
+            goto bail;
+        }
+    }
+
+    // At this point we've read everything and processed everything.  From here
+    // on out it's just writing output files.
+    if (SourcePos::hasErrors()) {
+        goto bail;
+    }
+
+    // Update symbols with information about which ones are needed as Java symbols.
+    assets->applyJavaSymbols();
+    if (SourcePos::hasErrors()) {
+        goto bail;
+    }
+
+    // If we've been asked to generate a dependency file, do that here
+    if (bundle->getGenDependencies()) {
+        // If this is the packaging step, generate the dependency file next to
+        // the output apk (e.g. bin/resources.ap_.d)
+        if (outputAPKFile) {
+            dependencyFile = String8(outputAPKFile);
+            // Add the .d extension to the dependency file.
+            dependencyFile.append(".d");
+        } else {
+            // Else if this is the R.java dependency generation step,
+            // generate the dependency file in the R.java package subdirectory
+            // e.g. gen/com/foo/app/R.java.d
+            dependencyFile = String8(bundle->getRClassDir());
+            dependencyFile.appendPath("R.java.d");
+        }
+        // Make sure we have a clean dependency file to start with
+        fp = fopen(dependencyFile, "w");
+        fclose(fp);
+    }
+
+    // Write out R.java constants
+    if (!assets->havePrivateSymbols()) {
+        if (bundle->getCustomPackage() == NULL) {
+            // Write the R.java file into the appropriate class directory
+            // e.g. gen/com/foo/app/R.java
+            err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
+        } else {
+            const String8 customPkg(bundle->getCustomPackage());
+            err = writeResourceSymbols(bundle, assets, customPkg, true);
+        }
+        if (err < 0) {
+            goto bail;
+        }
+        // If we have library files, we're going to write our R.java file into
+        // the appropriate class directory for those libraries as well.
+        // e.g. gen/com/foo/app/lib/R.java
+        if (bundle->getExtraPackages() != NULL) {
+            // Split on colon
+            String8 libs(bundle->getExtraPackages());
+            char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
+            while (packageString != NULL) {
+                // Write the R.java file out with the correct package name
+                err = writeResourceSymbols(bundle, assets, String8(packageString), true);
+                if (err < 0) {
+                    goto bail;
+                }
+                packageString = strtok(NULL, ":");
+            }
+            libs.unlockBuffer();
+        }
+    } else {
+        err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
+        if (err < 0) {
+            goto bail;
+        }
+        err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
+        if (err < 0) {
+            goto bail;
+        }
+    }
+
+    // Write out the ProGuard file
+    err = writeProguardFile(bundle, assets);
+    if (err < 0) {
+        goto bail;
+    }
+
+    // Write the apk
+    if (outputAPKFile) {
+        err = writeAPK(bundle, assets, String8(outputAPKFile));
+        if (err != NO_ERROR) {
+            fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
+            goto bail;
+        }
+    }
+
+    // If we've been asked to generate a dependency file, we need to finish up here.
+    // the writeResourceSymbols and writeAPK functions have already written the target
+    // half of the dependency file, now we need to write the prerequisites. (files that
+    // the R.java file or .ap_ file depend on)
+    if (bundle->getGenDependencies()) {
+        // Now that writeResourceSymbols or writeAPK has taken care of writing
+        // the targets to our dependency file, we'll write the prereqs
+        fp = fopen(dependencyFile, "a+");
+        fprintf(fp, " : ");
+        bool includeRaw = (outputAPKFile != NULL);
+        err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
+        // Also manually add the AndroidManifeset since it's not under res/ or assets/
+        // and therefore was not added to our pathstores during slurping
+        fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
+        fclose(fp);
+    }
+
+    retVal = 0;
+bail:
+    if (SourcePos::hasErrors()) {
+        SourcePos::printErrors(stderr);
+    }
+    return retVal;
+}
+
+/*
+ * Do PNG Crunching
+ * PRECONDITIONS
+ *  -S flag points to a source directory containing drawable* folders
+ *  -C flag points to destination directory. The folder structure in the
+ *     source directory will be mirrored to the destination (cache) directory
+ *
+ * POSTCONDITIONS
+ *  Destination directory will be updated to match the PNG files in
+ *  the source directory. 
+ */
+int doCrunch(Bundle* bundle)
+{
+    fprintf(stdout, "Crunching PNG Files in ");
+    fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
+    fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
+
+    updatePreProcessedCache(bundle);
+
+    return NO_ERROR;
+}
+
+/*
+ * Do PNG Crunching on a single flag
+ *  -i points to a single png file
+ *  -o points to a single png output file
+ */
+int doSingleCrunch(Bundle* bundle)
+{
+    fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
+    fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
+
+    String8 input(bundle->getSingleCrunchInputFile());
+    String8 output(bundle->getSingleCrunchOutputFile());
+
+    if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
+        // we can't return the status_t as it gets truncate to the lower 8 bits.
+        return 42;
+    }
+
+    return NO_ERROR;
+}
+
+char CONSOLE_DATA[2925] = {
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
+    86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
+    62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
+    81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
+    59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
+    59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
+    58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
+    47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
+    121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
+    81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
+    59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
+    59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
+    70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
+    81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
+    81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
+    32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
+    59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
+    60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
+    61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
+    61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
+    46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
+    59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
+    109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
+    67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+    59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
+    61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
+    59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
+    73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
+    59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
+    32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
+    59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
+    46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
+    97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
+    46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
+    119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
+    59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
+    32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
+    119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+    59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
+    81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
+    87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
+    81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
+    45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
+    81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
+    59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
+    59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
+    81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+    81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
+    81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
+    32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
+    81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
+    61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
+    81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+    81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
+    59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
+    59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
+    58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
+    61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
+    59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
+    32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
+    61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
+    59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
+    59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
+    59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
+    46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
+    59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
+    59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
+    32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
+    59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
+    59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
+  };
diff --git a/tools/aapt/CrunchCache.cpp b/tools/aapt/CrunchCache.cpp
new file mode 100644
index 0000000..c4cf6bc
--- /dev/null
+++ b/tools/aapt/CrunchCache.cpp
@@ -0,0 +1,104 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Implementation file for CrunchCache
+// This file defines functions laid out and documented in
+// CrunchCache.h
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+#include "CrunchCache.h"
+
+using namespace android;
+
+CrunchCache::CrunchCache(String8 sourcePath, String8 destPath, FileFinder* ff)
+    : mSourcePath(sourcePath), mDestPath(destPath), mSourceFiles(0), mDestFiles(0), mFileFinder(ff)
+{
+    // We initialize the default value to return to 0 so if a file doesn't exist
+    // then all files are automatically "newer" than it.
+
+    // Set file extensions to look for. Right now just pngs.
+    mExtensions.push(String8(".png"));
+
+    // Load files into our data members
+    loadFiles();
+}
+
+size_t CrunchCache::crunch(CacheUpdater* cu, bool forceOverwrite)
+{
+    size_t numFilesUpdated = 0;
+
+    // Iterate through the source files and compare to cache.
+    // After processing a file, remove it from the source files and
+    // from the dest files.
+    // We're done when we're out of files in source.
+    String8 relativePath;
+    while (mSourceFiles.size() > 0) {
+        // Get the full path to the source file, then convert to a c-string
+        // and offset our beginning pointer to the length of the sourcePath
+        // This efficiently strips the source directory prefix from our path.
+        // Also, String8 doesn't have a substring method so this is what we've
+        // got to work with.
+        const char* rPathPtr = mSourceFiles.keyAt(0).string()+mSourcePath.length();
+        // Strip leading slash if present
+        int offset = 0;
+        if (rPathPtr[0] == OS_PATH_SEPARATOR)
+            offset = 1;
+        relativePath = String8(rPathPtr + offset);
+
+        if (forceOverwrite || needsUpdating(relativePath)) {
+            cu->processImage(mSourcePath.appendPathCopy(relativePath),
+                             mDestPath.appendPathCopy(relativePath));
+            numFilesUpdated++;
+            // crunchFile(relativePath);
+        }
+        // Delete this file from the source files and (if it exists) from the
+        // dest files.
+        mSourceFiles.removeItemsAt(0);
+        mDestFiles.removeItem(mDestPath.appendPathCopy(relativePath));
+    }
+
+    // Iterate through what's left of destFiles and delete leftovers
+    while (mDestFiles.size() > 0) {
+        cu->deleteFile(mDestFiles.keyAt(0));
+        mDestFiles.removeItemsAt(0);
+    }
+
+    // Update our knowledge of the files cache
+    // both source and dest should be empty by now.
+    loadFiles();
+
+    return numFilesUpdated;
+}
+
+void CrunchCache::loadFiles()
+{
+    // Clear out our data structures to avoid putting in duplicates
+    mSourceFiles.clear();
+    mDestFiles.clear();
+
+    // Make a directory walker that points to the system.
+    DirectoryWalker* dw = new SystemDirectoryWalker();
+
+    // Load files in the source directory
+    mFileFinder->findFiles(mSourcePath, mExtensions, mSourceFiles,dw);
+
+    // Load files in the destination directory
+    mFileFinder->findFiles(mDestPath,mExtensions,mDestFiles,dw);
+
+    delete dw;
+}
+
+bool CrunchCache::needsUpdating(String8 relativePath) const
+{
+    // Retrieve modification dates for this file entry under the source and
+    // cache directory trees. The vectors will return a modification date of 0
+    // if the file doesn't exist.
+    time_t sourceDate = mSourceFiles.valueFor(mSourcePath.appendPathCopy(relativePath));
+    time_t destDate = mDestFiles.valueFor(mDestPath.appendPathCopy(relativePath));
+    return sourceDate > destDate;
+}
\ No newline at end of file
diff --git a/tools/aapt/CrunchCache.h b/tools/aapt/CrunchCache.h
new file mode 100644
index 0000000..be3da5c
--- /dev/null
+++ b/tools/aapt/CrunchCache.h
@@ -0,0 +1,102 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Cache manager for pre-processed PNG files.
+// Contains code for managing which PNG files get processed
+// at build time.
+//
+
+#ifndef CRUNCHCACHE_H
+#define CRUNCHCACHE_H
+
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+
+using namespace android;
+
+/** CrunchCache
+ *  This class is a cache manager which can pre-process PNG files and store
+ *  them in a mirror-cache. It's capable of doing incremental updates to its
+ *  cache.
+ *
+ *  Usage:
+ *      Create an instance initialized with the root of the source tree, the
+ *      root location to store the cache files, and an instance of a file finder.
+ *      Then update the cache by calling crunch.
+ */
+class CrunchCache {
+public:
+    // Constructor
+    CrunchCache(String8 sourcePath, String8 destPath, FileFinder* ff);
+
+    // Nobody should be calling the default constructor
+    // So this space is intentionally left blank
+
+    // Default Copy Constructor and Destructor are fine
+
+    /** crunch is the workhorse of this class.
+     * It goes through all the files found in the sourcePath and compares
+     * them to the cached versions in the destPath. If the optional
+     * argument forceOverwrite is set to true, then all source files are
+     * re-crunched even if they have not been modified recently. Otherwise,
+     * source files are only crunched when they needUpdating. Afterwards,
+     * we delete any leftover files in the cache that are no longer present
+     * in source.
+     *
+     * PRECONDITIONS:
+     *      No setup besides construction is needed
+     * POSTCONDITIONS:
+     *      The cache is updated to fully reflect all changes in source.
+     *      The function then returns the number of files changed in cache
+     *      (counting deletions).
+     */
+    size_t crunch(CacheUpdater* cu, bool forceOverwrite=false);
+
+private:
+    /** loadFiles is a wrapper to the FileFinder that places matching
+     * files into mSourceFiles and mDestFiles.
+     *
+     *  POSTCONDITIONS
+     *      mDestFiles and mSourceFiles are refreshed to reflect the current
+     *      state of the files in the source and dest directories.
+     *      Any previous contents of mSourceFiles and mDestFiles are cleared.
+     */
+    void loadFiles();
+
+    /** needsUpdating takes a file path
+     * and returns true if the file represented by this path is newer in the
+     * sourceFiles than in the cache (mDestFiles).
+     *
+     * PRECONDITIONS:
+     *      mSourceFiles and mDestFiles must be initialized and filled.
+     * POSTCONDITIONS:
+     *      returns true if and only if source file's modification time
+     *      is greater than the cached file's mod-time. Otherwise returns false.
+     *
+     * USAGE:
+     *      Should be used something like the following:
+     *      if (needsUpdating(filePath))
+     *          // Recrunch sourceFile out to destFile.
+     *
+     */
+    bool needsUpdating(String8 relativePath) const;
+
+    // DATA MEMBERS ====================================================
+
+    String8 mSourcePath;
+    String8 mDestPath;
+
+    Vector<String8> mExtensions;
+
+    // Each vector of paths contains one entry per PNG file encountered.
+    // Each entry consists of a path pointing to that PNG.
+    DefaultKeyedVector<String8,time_t> mSourceFiles;
+    DefaultKeyedVector<String8,time_t> mDestFiles;
+
+    // Pointer to a FileFinder to use
+    FileFinder* mFileFinder;
+};
+
+#endif // CRUNCHCACHE_H
diff --git a/tools/aapt/DirectoryWalker.h b/tools/aapt/DirectoryWalker.h
new file mode 100644
index 0000000..88031d0
--- /dev/null
+++ b/tools/aapt/DirectoryWalker.h
@@ -0,0 +1,98 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Defines an abstraction for opening a directory on the filesystem and
+// iterating through it.
+
+#ifndef DIRECTORYWALKER_H
+#define DIRECTORYWALKER_H
+
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utils/String8.h>
+
+#include <stdio.h>
+
+using namespace android;
+
+// Directory Walker
+// This is an abstraction for walking through a directory and getting files
+// and descriptions.
+
+class DirectoryWalker {
+public:
+    virtual ~DirectoryWalker() {};
+    virtual bool openDir(String8 path) = 0;
+    virtual bool openDir(const char* path) = 0;
+    // Advance to next directory entry
+    virtual struct dirent* nextEntry() = 0;
+    // Get the stats for the current entry
+    virtual struct stat*   entryStats() = 0;
+    // Clean Up
+    virtual void closeDir() = 0;
+    // This class is able to replicate itself on the heap
+    virtual DirectoryWalker* clone() = 0;
+
+    // DATA MEMBERS
+    // Current directory entry
+    struct dirent mEntry;
+    // Stats for that directory entry
+    struct stat mStats;
+    // Base path
+    String8 mBasePath;
+};
+
+// System Directory Walker
+// This is an implementation of the above abstraction that calls
+// real system calls and is fully functional.
+// functions are inlined since they're very short and simple
+
+class SystemDirectoryWalker : public DirectoryWalker {
+
+    // Default constructor, copy constructor, and destructor are fine
+public:
+    virtual bool openDir(String8 path) {
+        mBasePath = path;
+        dir = NULL;
+        dir = opendir(mBasePath.string() );
+
+        if (dir == NULL)
+            return false;
+
+        return true;
+    };
+    virtual bool openDir(const char* path) {
+        String8 p(path);
+        openDir(p);
+        return true;
+    };
+    // Advance to next directory entry
+    virtual struct dirent* nextEntry() {
+        struct dirent* entryPtr = readdir(dir);
+        if (entryPtr == NULL)
+            return NULL;
+
+        mEntry = *entryPtr;
+        // Get stats
+        String8 fullPath = mBasePath.appendPathCopy(mEntry.d_name);
+        stat(fullPath.string(),&mStats);
+        return &mEntry;
+    };
+    // Get the stats for the current entry
+    virtual struct stat*   entryStats() {
+        return &mStats;
+    };
+    virtual void closeDir() {
+        closedir(dir);
+    };
+    virtual DirectoryWalker* clone() {
+        return new SystemDirectoryWalker(*this);
+    };
+private:
+    DIR* dir;
+};
+
+#endif // DIRECTORYWALKER_H
diff --git a/tools/aapt/FileFinder.cpp b/tools/aapt/FileFinder.cpp
new file mode 100644
index 0000000..18775c0
--- /dev/null
+++ b/tools/aapt/FileFinder.cpp
@@ -0,0 +1,98 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+// File Finder implementation.
+// Implementation for the functions declared and documented in FileFinder.h
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+#include <utils/KeyedVector.h>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "DirectoryWalker.h"
+#include "FileFinder.h"
+
+//#define DEBUG
+
+using android::String8;
+
+// Private function to check whether a file is a directory or not
+bool isDirectory(const char* filename) {
+    struct stat fileStat;
+    if (stat(filename, &fileStat) == -1) {
+        return false;
+    }
+    return(S_ISDIR(fileStat.st_mode));
+}
+
+
+// Private function to check whether a file is a regular file or not
+bool isFile(const char* filename) {
+    struct stat fileStat;
+    if (stat(filename, &fileStat) == -1) {
+        return false;
+    }
+    return(S_ISREG(fileStat.st_mode));
+}
+
+bool SystemFileFinder::findFiles(String8 basePath, Vector<String8>& extensions,
+                                 KeyedVector<String8,time_t>& fileStore,
+                                 DirectoryWalker* dw)
+{
+    // Scan the directory pointed to by basePath
+    // check files and recurse into subdirectories.
+    if (!dw->openDir(basePath)) {
+        return false;
+    }
+    /*
+     *  Go through all directory entries. Check each file using checkAndAddFile
+     *  and recurse into sub-directories.
+     */
+    struct dirent* entry;
+    while ((entry = dw->nextEntry()) != NULL) {
+        String8 entryName(entry->d_name);
+        if (entry->d_name[0] == '.') // Skip hidden files and directories
+            continue;
+
+        String8 fullPath = basePath.appendPathCopy(entryName);
+        // If this entry is a directory we'll recurse into it
+        if (isDirectory(fullPath.string()) ) {
+            DirectoryWalker* copy = dw->clone();
+            findFiles(fullPath, extensions, fileStore,copy);
+            delete copy;
+        }
+
+        // If this entry is a file, we'll pass it over to checkAndAddFile
+        if (isFile(fullPath.string()) ) {
+            checkAndAddFile(fullPath,dw->entryStats(),extensions,fileStore);
+        }
+    }
+
+    // Clean up
+    dw->closeDir();
+
+    return true;
+}
+
+void SystemFileFinder::checkAndAddFile(String8 path, const struct stat* stats,
+                                       Vector<String8>& extensions,
+                                       KeyedVector<String8,time_t>& fileStore)
+{
+    // Loop over the extensions, checking for a match
+    bool done = false;
+    String8 ext(path.getPathExtension());
+    ext.toLower();
+    for (size_t i = 0; i < extensions.size() && !done; ++i) {
+        String8 ext2 = extensions[i].getPathExtension();
+        ext2.toLower();
+        // Compare the extensions. If a match is found, add to storage.
+        if (ext == ext2) {
+            done = true;
+            fileStore.add(path,stats->st_mtime);
+        }
+    }
+}
+
diff --git a/tools/aapt/FileFinder.h b/tools/aapt/FileFinder.h
new file mode 100644
index 0000000..6974aee
--- /dev/null
+++ b/tools/aapt/FileFinder.h
@@ -0,0 +1,80 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+// File Finder.
+// This is a collection of useful functions for finding paths and modification
+// times of files that match an extension pattern in a directory tree.
+// and finding files in it.
+
+#ifndef FILEFINDER_H
+#define FILEFINDER_H
+
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+
+using namespace android;
+
+// Abstraction to allow for dependency injection. See MockFileFinder.h
+// for the testing implementation.
+class FileFinder {
+public:
+    virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+                           KeyedVector<String8,time_t>& fileStore,
+                           DirectoryWalker* dw) = 0;
+
+    virtual ~FileFinder() {};
+};
+
+class SystemFileFinder : public FileFinder {
+public:
+
+    /* findFiles takes a path, a Vector of extensions, and a destination KeyedVector
+     *           and places path/modification date key/values pointing to
+     *          all files with matching extensions found into the KeyedVector
+     * PRECONDITIONS
+     *     path is a valid system path
+     *     extensions should include leading "."
+     *                This is not necessary, but the comparison directly
+     *                compares the end of the path string so if the "."
+     *                is excluded there is a small chance you could have
+     *                a false positive match. (For example: extension "png"
+     *                would match a file called "blahblahpng")
+     *
+     * POSTCONDITIONS
+     *     fileStore contains (in no guaranteed order) paths to all
+     *                matching files encountered in subdirectories of path
+     *                as keys in the KeyedVector. Each key has the modification time
+     *                of the file as its value.
+     *
+     * Calls checkAndAddFile on each file encountered in the directory tree
+     * Recursively descends into subdirectories.
+     */
+    virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+                           KeyedVector<String8,time_t>& fileStore,
+                           DirectoryWalker* dw);
+
+private:
+    /**
+     * checkAndAddFile looks at a single file path and stat combo
+     * to determine whether it is a matching file (by looking at
+     * the extension)
+     *
+     * PRECONDITIONS
+     *    no setup is needed
+     *
+     * POSTCONDITIONS
+     *    If the given file has a matching extension then a new entry
+     *    is added to the KeyedVector with the path as the key and the modification
+     *    time as the value.
+     *
+     */
+    static void checkAndAddFile(String8 path, const struct stat* stats,
+                                Vector<String8>& extensions,
+                                KeyedVector<String8,time_t>& fileStore);
+
+};
+#endif // FILEFINDER_H
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
new file mode 100644
index 0000000..b2cbf49
--- /dev/null
+++ b/tools/aapt/Images.cpp
@@ -0,0 +1,1387 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#define PNG_INTERNAL
+
+#include "Images.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/ByteOrder.h>
+
+#include <png.h>
+#include <zlib.h>
+
+#define NOISY(x) //x
+
+static void
+png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+    AaptFile* aaptfile = (AaptFile*) png_get_io_ptr(png_ptr);
+    status_t err = aaptfile->writeData(data, length);
+    if (err != NO_ERROR) {
+        png_error(png_ptr, "Write Error");
+    }
+}
+
+
+static void
+png_flush_aapt_file(png_structp png_ptr)
+{
+}
+
+// This holds an image as 8bpp RGBA.
+struct image_info
+{
+    image_info() : rows(NULL), is9Patch(false), allocRows(NULL) { }
+    ~image_info() {
+        if (rows && rows != allocRows) {
+            free(rows);
+        }
+        if (allocRows) {
+            for (int i=0; i<(int)allocHeight; i++) {
+                free(allocRows[i]);
+            }
+            free(allocRows);
+        }
+        free(info9Patch.xDivs);
+        free(info9Patch.yDivs);
+        free(info9Patch.colors);
+    }
+
+    png_uint_32 width;
+    png_uint_32 height;
+    png_bytepp rows;
+
+    // 9-patch info.
+    bool is9Patch;
+    Res_png_9patch info9Patch;
+
+    // Layout padding, if relevant
+    bool haveLayoutBounds;
+    int32_t layoutBoundsLeft;
+    int32_t layoutBoundsTop;
+    int32_t layoutBoundsRight;
+    int32_t layoutBoundsBottom;
+
+    png_uint_32 allocHeight;
+    png_bytepp allocRows;
+};
+
+static void read_png(const char* imageName,
+                     png_structp read_ptr, png_infop read_info,
+                     image_info* outImageInfo)
+{
+    int color_type;
+    int bit_depth, interlace_type, compression_type;
+    int i;
+
+    png_read_info(read_ptr, read_info);
+
+    png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
+       &outImageInfo->height, &bit_depth, &color_type,
+       &interlace_type, &compression_type, NULL);
+
+    //printf("Image %s:\n", imageName);
+    //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
+    //       color_type, bit_depth, interlace_type, compression_type);
+
+    if (color_type == PNG_COLOR_TYPE_PALETTE)
+        png_set_palette_to_rgb(read_ptr);
+
+    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+        png_set_expand_gray_1_2_4_to_8(read_ptr);
+
+    if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) {
+        //printf("Has PNG_INFO_tRNS!\n");
+        png_set_tRNS_to_alpha(read_ptr);
+    }
+
+    if (bit_depth == 16)
+        png_set_strip_16(read_ptr);
+
+    if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
+        png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
+
+    if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+        png_set_gray_to_rgb(read_ptr);
+
+    png_read_update_info(read_ptr, read_info);
+
+    outImageInfo->rows = (png_bytepp)malloc(
+        outImageInfo->height * sizeof(png_bytep));
+    outImageInfo->allocHeight = outImageInfo->height;
+    outImageInfo->allocRows = outImageInfo->rows;
+
+    png_set_rows(read_ptr, read_info, outImageInfo->rows);
+
+    for (i = 0; i < (int)outImageInfo->height; i++)
+    {
+        outImageInfo->rows[i] = (png_bytep)
+            malloc(png_get_rowbytes(read_ptr, read_info));
+    }
+
+    png_read_image(read_ptr, outImageInfo->rows);
+
+    png_read_end(read_ptr, read_info);
+
+    NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
+                 imageName,
+                 (int)outImageInfo->width, (int)outImageInfo->height,
+                 bit_depth, color_type,
+                 interlace_type, compression_type));
+
+    png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
+       &outImageInfo->height, &bit_depth, &color_type,
+       &interlace_type, &compression_type, NULL);
+}
+
+#define COLOR_TRANSPARENT 0
+#define COLOR_WHITE 0xFFFFFFFF
+#define COLOR_TICK  0xFF000000
+#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF
+
+enum {
+    TICK_TYPE_NONE,
+    TICK_TYPE_TICK,
+    TICK_TYPE_LAYOUT_BOUNDS,
+    TICK_TYPE_BOTH
+};
+
+static int tick_type(png_bytep p, bool transparent, const char** outError)
+{
+    png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+    if (transparent) {
+        if (p[3] == 0) {
+            return TICK_TYPE_NONE;
+        }
+        if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+            return TICK_TYPE_LAYOUT_BOUNDS;
+        }
+        if (color == COLOR_TICK) {
+            return TICK_TYPE_TICK;
+        }
+
+        // Error cases
+        if (p[3] != 0xff) {
+            *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
+            return TICK_TYPE_NONE;
+        }
+        if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+            *outError = "Ticks in transparent frame must be black or red";
+        }
+        return TICK_TYPE_TICK;
+    }
+
+    if (p[3] != 0xFF) {
+        *outError = "White frame must be a solid color (no alpha)";
+    }
+    if (color == COLOR_WHITE) {
+        return TICK_TYPE_NONE;
+    }
+    if (color == COLOR_TICK) {
+        return TICK_TYPE_TICK;
+    }
+    if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+        return TICK_TYPE_LAYOUT_BOUNDS;
+    }
+
+    if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+        *outError = "Ticks in white frame must be black or red";
+        return TICK_TYPE_NONE;
+    }
+    return TICK_TYPE_TICK;
+}
+
+enum {
+    TICK_START,
+    TICK_INSIDE_1,
+    TICK_OUTSIDE_1
+};
+
+static status_t get_horizontal_ticks(
+        png_bytep row, int width, bool transparent, bool required,
+        int32_t* outLeft, int32_t* outRight, const char** outError,
+        uint8_t* outDivs, bool multipleAllowed)
+{
+    int i;
+    *outLeft = *outRight = -1;
+    int state = TICK_START;
+    bool found = false;
+
+    for (i=1; i<width-1; i++) {
+        if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) {
+            if (state == TICK_START ||
+                (state == TICK_OUTSIDE_1 && multipleAllowed)) {
+                *outLeft = i-1;
+                *outRight = width-2;
+                found = true;
+                if (outDivs != NULL) {
+                    *outDivs += 2;
+                }
+                state = TICK_INSIDE_1;
+            } else if (state == TICK_OUTSIDE_1) {
+                *outError = "Can't have more than one marked region along edge";
+                *outLeft = i;
+                return UNKNOWN_ERROR;
+            }
+        } else if (*outError == NULL) {
+            if (state == TICK_INSIDE_1) {
+                // We're done with this div.  Move on to the next.
+                *outRight = i-1;
+                outRight += 2;
+                outLeft += 2;
+                state = TICK_OUTSIDE_1;
+            }
+        } else {
+            *outLeft = i;
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    if (required && !found) {
+        *outError = "No marked region found along edge";
+        *outLeft = -1;
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+static status_t get_vertical_ticks(
+        png_bytepp rows, int offset, int height, bool transparent, bool required,
+        int32_t* outTop, int32_t* outBottom, const char** outError,
+        uint8_t* outDivs, bool multipleAllowed)
+{
+    int i;
+    *outTop = *outBottom = -1;
+    int state = TICK_START;
+    bool found = false;
+
+    for (i=1; i<height-1; i++) {
+        if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) {
+            if (state == TICK_START ||
+                (state == TICK_OUTSIDE_1 && multipleAllowed)) {
+                *outTop = i-1;
+                *outBottom = height-2;
+                found = true;
+                if (outDivs != NULL) {
+                    *outDivs += 2;
+                }
+                state = TICK_INSIDE_1;
+            } else if (state == TICK_OUTSIDE_1) {
+                *outError = "Can't have more than one marked region along edge";
+                *outTop = i;
+                return UNKNOWN_ERROR;
+            }
+        } else if (*outError == NULL) {
+            if (state == TICK_INSIDE_1) {
+                // We're done with this div.  Move on to the next.
+                *outBottom = i-1;
+                outTop += 2;
+                outBottom += 2;
+                state = TICK_OUTSIDE_1;
+            }
+        } else {
+            *outTop = i;
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    if (required && !found) {
+        *outError = "No marked region found along edge";
+        *outTop = -1;
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+static status_t get_horizontal_layout_bounds_ticks(
+        png_bytep row, int width, bool transparent, bool required,
+        int32_t* outLeft, int32_t* outRight, const char** outError)
+{
+    int i;
+    *outLeft = *outRight = 0;
+
+    // Look for left tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) {
+        // Starting with a layout padding tick
+        i = 1;
+        while (i < width - 1) {
+            (*outLeft)++;
+            i++;
+            int tick = tick_type(row + i * 4, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    // Look for right tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) {
+        // Ending with a layout padding tick
+        i = width - 2;
+        while (i > 1) {
+            (*outRight)++;
+            i--;
+            int tick = tick_type(row+i*4, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+static status_t get_vertical_layout_bounds_ticks(
+        png_bytepp rows, int offset, int height, bool transparent, bool required,
+        int32_t* outTop, int32_t* outBottom, const char** outError)
+{
+    int i;
+    *outTop = *outBottom = 0;
+
+    // Look for top tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) {
+        // Starting with a layout padding tick
+        i = 1;
+        while (i < height - 1) {
+            (*outTop)++;
+            i++;
+            int tick = tick_type(rows[i] + offset, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    // Look for bottom tick
+    if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) {
+        // Ending with a layout padding tick
+        i = height - 2;
+        while (i > 1) {
+            (*outBottom)++;
+            i--;
+            int tick = tick_type(rows[i] + offset, transparent, outError);
+            if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+                break;
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+
+static uint32_t get_color(
+    png_bytepp rows, int left, int top, int right, int bottom)
+{
+    png_bytep color = rows[top] + left*4;
+
+    if (left > right || top > bottom) {
+        return Res_png_9patch::TRANSPARENT_COLOR;
+    }
+
+    while (top <= bottom) {
+        for (int i = left; i <= right; i++) {
+            png_bytep p = rows[top]+i*4;
+            if (color[3] == 0) {
+                if (p[3] != 0) {
+                    return Res_png_9patch::NO_COLOR;
+                }
+            } else if (p[0] != color[0] || p[1] != color[1]
+                       || p[2] != color[2] || p[3] != color[3]) {
+                return Res_png_9patch::NO_COLOR;
+            }
+        }
+        top++;
+    }
+
+    if (color[3] == 0) {
+        return Res_png_9patch::TRANSPARENT_COLOR;
+    }
+    return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+}
+
+static void select_patch(
+    int which, int front, int back, int size, int* start, int* end)
+{
+    switch (which) {
+    case 0:
+        *start = 0;
+        *end = front-1;
+        break;
+    case 1:
+        *start = front;
+        *end = back-1;
+        break;
+    case 2:
+        *start = back;
+        *end = size-1;
+        break;
+    }
+}
+
+static uint32_t get_color(image_info* image, int hpatch, int vpatch)
+{
+    int left, right, top, bottom;
+    select_patch(
+        hpatch, image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
+        image->width, &left, &right);
+    select_patch(
+        vpatch, image->info9Patch.yDivs[0], image->info9Patch.yDivs[1],
+        image->height, &top, &bottom);
+    //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
+    //       hpatch, vpatch, left, top, right, bottom);
+    const uint32_t c = get_color(image->rows, left, top, right, bottom);
+    NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c));
+    return c;
+}
+
+static status_t do_9patch(const char* imageName, image_info* image)
+{
+    image->is9Patch = true;
+
+    int W = image->width;
+    int H = image->height;
+    int i, j;
+
+    int maxSizeXDivs = W * sizeof(int32_t);
+    int maxSizeYDivs = H * sizeof(int32_t);
+    int32_t* xDivs = (int32_t*) malloc(maxSizeXDivs);
+    int32_t* yDivs = (int32_t*) malloc(maxSizeYDivs);
+    uint8_t  numXDivs = 0;
+    uint8_t  numYDivs = 0;
+    int8_t numColors;
+    int numRows;
+    int numCols;
+    int top;
+    int left;
+    int right;
+    int bottom;
+    memset(xDivs, -1, maxSizeXDivs);
+    memset(yDivs, -1, maxSizeYDivs);
+    image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
+        image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+
+    image->layoutBoundsLeft = image->layoutBoundsRight =
+        image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+    png_bytep p = image->rows[0];
+    bool transparent = p[3] == 0;
+    bool hasColor = false;
+
+    const char* errorMsg = NULL;
+    int errorPixel = -1;
+    const char* errorEdge = NULL;
+
+    int colorIndex = 0;
+
+    // Validate size...
+    if (W < 3 || H < 3) {
+        errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+        goto getout;
+    }
+
+    // Validate frame...
+    if (!transparent &&
+        (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+        errorMsg = "Must have one-pixel frame that is either transparent or white";
+        goto getout;
+    }
+
+    // Find left and right of sizing areas...
+    if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0],
+                             &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) {
+        errorPixel = xDivs[0];
+        errorEdge = "top";
+        goto getout;
+    }
+
+    // Find top and bottom of sizing areas...
+    if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0],
+                           &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) {
+        errorPixel = yDivs[0];
+        errorEdge = "left";
+        goto getout;
+    }
+
+    // Find left and right of padding area...
+    if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft,
+                             &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) {
+        errorPixel = image->info9Patch.paddingLeft;
+        errorEdge = "bottom";
+        goto getout;
+    }
+
+    // Find top and bottom of padding area...
+    if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop,
+                           &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) {
+        errorPixel = image->info9Patch.paddingTop;
+        errorEdge = "right";
+        goto getout;
+    }
+
+    // Find left and right of layout padding...
+    get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false,
+                                        &image->layoutBoundsLeft,
+                                        &image->layoutBoundsRight, &errorMsg);
+
+    get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false,
+                                        &image->layoutBoundsTop,
+                                        &image->layoutBoundsBottom, &errorMsg);
+
+    image->haveLayoutBounds = image->layoutBoundsLeft != 0
+                               || image->layoutBoundsRight != 0
+                               || image->layoutBoundsTop != 0
+                               || image->layoutBoundsBottom != 0;
+
+    if (image->haveLayoutBounds) {
+        NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+                image->layoutBoundsRight, image->layoutBoundsBottom));
+    }
+
+    // Copy patch data into image
+    image->info9Patch.numXDivs = numXDivs;
+    image->info9Patch.numYDivs = numYDivs;
+    image->info9Patch.xDivs = xDivs;
+    image->info9Patch.yDivs = yDivs;
+
+    // If padding is not yet specified, take values from size.
+    if (image->info9Patch.paddingLeft < 0) {
+        image->info9Patch.paddingLeft = xDivs[0];
+        image->info9Patch.paddingRight = W - 2 - xDivs[1];
+    } else {
+        // Adjust value to be correct!
+        image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+    }
+    if (image->info9Patch.paddingTop < 0) {
+        image->info9Patch.paddingTop = yDivs[0];
+        image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+    } else {
+        // Adjust value to be correct!
+        image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+    }
+
+    NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+                 image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
+                 image->info9Patch.yDivs[0], image->info9Patch.yDivs[1]));
+    NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+                 image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+                 image->info9Patch.paddingTop, image->info9Patch.paddingBottom));
+
+    // Remove frame from image.
+    image->rows = (png_bytepp)malloc((H-2) * sizeof(png_bytep));
+    for (i=0; i<(H-2); i++) {
+        image->rows[i] = image->allocRows[i+1];
+        memmove(image->rows[i], image->rows[i]+4, (W-2)*4);
+    }
+    image->width -= 2;
+    W = image->width;
+    image->height -= 2;
+    H = image->height;
+
+    // Figure out the number of rows and columns in the N-patch
+    numCols = numXDivs + 1;
+    if (xDivs[0] == 0) {  // Column 1 is strechable
+        numCols--;
+    }
+    if (xDivs[numXDivs - 1] == W) {
+        numCols--;
+    }
+    numRows = numYDivs + 1;
+    if (yDivs[0] == 0) {  // Row 1 is strechable
+        numRows--;
+    }
+    if (yDivs[numYDivs - 1] == H) {
+        numRows--;
+    }
+
+    // Make sure the amount of rows and columns will fit in the number of
+    // colors we can use in the 9-patch format.
+    if (numRows * numCols > 0x7F) {
+        errorMsg = "Too many rows and columns in 9-patch perimeter";
+        goto getout;
+    }
+
+    numColors = numRows * numCols;
+    image->info9Patch.numColors = numColors;
+    image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
+
+    // Fill in color information for each patch.
+
+    uint32_t c;
+    top = 0;
+
+    // The first row always starts with the top being at y=0 and the bottom
+    // being either yDivs[1] (if yDivs[0]=0) of yDivs[0].  In the former case
+    // the first row is stretchable along the Y axis, otherwise it is fixed.
+    // The last row always ends with the bottom being bitmap.height and the top
+    // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+    // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+    // the Y axis, otherwise it is fixed.
+    //
+    // The first and last columns are similarly treated with respect to the X
+    // axis.
+    //
+    // The above is to help explain some of the special casing that goes on the
+    // code below.
+
+    // The initial yDiv and whether the first row is considered stretchable or
+    // not depends on whether yDiv[0] was zero or not.
+    for (j = (yDivs[0] == 0 ? 1 : 0);
+          j <= numYDivs && top < H;
+          j++) {
+        if (j == numYDivs) {
+            bottom = H;
+        } else {
+            bottom = yDivs[j];
+        }
+        left = 0;
+        // The initial xDiv and whether the first column is considered
+        // stretchable or not depends on whether xDiv[0] was zero or not.
+        for (i = xDivs[0] == 0 ? 1 : 0;
+              i <= numXDivs && left < W;
+              i++) {
+            if (i == numXDivs) {
+                right = W;
+            } else {
+                right = xDivs[i];
+            }
+            c = get_color(image->rows, left, top, right - 1, bottom - 1);
+            image->info9Patch.colors[colorIndex++] = c;
+            NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true);
+            left = right;
+        }
+        top = bottom;
+    }
+
+    assert(colorIndex == numColors);
+
+    for (i=0; i<numColors; i++) {
+        if (hasColor) {
+            if (i == 0) printf("Colors in %s:\n ", imageName);
+            printf(" #%08x", image->info9Patch.colors[i]);
+            if (i == numColors - 1) printf("\n");
+        }
+    }
+
+    image->is9Patch = true;
+    image->info9Patch.deviceToFile();
+
+getout:
+    if (errorMsg) {
+        fprintf(stderr,
+            "ERROR: 9-patch image %s malformed.\n"
+            "       %s.\n", imageName, errorMsg);
+        if (errorEdge != NULL) {
+            if (errorPixel >= 0) {
+                fprintf(stderr,
+                    "       Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
+            } else {
+                fprintf(stderr,
+                    "       Found along %s edge.\n", errorEdge);
+            }
+        }
+        return UNKNOWN_ERROR;
+    }
+    return NO_ERROR;
+}
+
+static void checkNinePatchSerialization(Res_png_9patch* inPatch,  void * data)
+{
+    if (sizeof(void*) != sizeof(int32_t)) {
+        // can't deserialize on a non-32 bit system
+        return;
+    }
+    size_t patchSize = inPatch->serializedSize();
+    void * newData = malloc(patchSize);
+    memcpy(newData, data, patchSize);
+    Res_png_9patch* outPatch = inPatch->deserialize(newData);
+    // deserialization is done in place, so outPatch == newData
+    assert(outPatch == newData);
+    assert(outPatch->numXDivs == inPatch->numXDivs);
+    assert(outPatch->numYDivs == inPatch->numYDivs);
+    assert(outPatch->paddingLeft == inPatch->paddingLeft);
+    assert(outPatch->paddingRight == inPatch->paddingRight);
+    assert(outPatch->paddingTop == inPatch->paddingTop);
+    assert(outPatch->paddingBottom == inPatch->paddingBottom);
+    for (int i = 0; i < outPatch->numXDivs; i++) {
+        assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
+    }
+    for (int i = 0; i < outPatch->numYDivs; i++) {
+        assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
+    }
+    for (int i = 0; i < outPatch->numColors; i++) {
+        assert(outPatch->colors[i] == inPatch->colors[i]);
+    }
+    free(newData);
+}
+
+static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) {
+    if (!(patch1.numXDivs == patch2.numXDivs &&
+          patch1.numYDivs == patch2.numYDivs &&
+          patch1.numColors == patch2.numColors &&
+          patch1.paddingLeft == patch2.paddingLeft &&
+          patch1.paddingRight == patch2.paddingRight &&
+          patch1.paddingTop == patch2.paddingTop &&
+          patch1.paddingBottom == patch2.paddingBottom)) {
+            return false;
+    }
+    for (int i = 0; i < patch1.numColors; i++) {
+        if (patch1.colors[i] != patch2.colors[i]) {
+            return false;
+        }
+    }
+    for (int i = 0; i < patch1.numXDivs; i++) {
+        if (patch1.xDivs[i] != patch2.xDivs[i]) {
+            return false;
+        }
+    }
+    for (int i = 0; i < patch1.numYDivs; i++) {
+        if (patch1.yDivs[i] != patch2.yDivs[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static void dump_image(int w, int h, png_bytepp rows, int color_type)
+{
+    int i, j, rr, gg, bb, aa;
+
+    int bpp;
+    if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+        bpp = 1;
+    } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        bpp = 2;
+    } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+        // We use a padding byte even when there is no alpha
+        bpp = 4;
+    } else {
+        printf("Unknown color type %d.\n", color_type);
+    }
+
+    for (j = 0; j < h; j++) {
+        png_bytep row = rows[j];
+        for (i = 0; i < w; i++) {
+            rr = row[0];
+            gg = row[1];
+            bb = row[2];
+            aa = row[3];
+            row += bpp;
+
+            if (i == 0) {
+                printf("Row %d:", j);
+            }
+            switch (bpp) {
+            case 1:
+                printf(" (%d)", rr);
+                break;
+            case 2:
+                printf(" (%d %d", rr, gg);
+                break;
+            case 3:
+                printf(" (%d %d %d)", rr, gg, bb);
+                break;
+            case 4:
+                printf(" (%d %d %d %d)", rr, gg, bb, aa);
+                break;
+            }
+            if (i == (w - 1)) {
+                NOISY(printf("\n"));
+            }
+        }
+    }
+}
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a)   ((a)<0?-(a):(a))
+
+static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance,
+                          png_colorp rgbPalette, png_bytep alphaPalette,
+                          int *paletteEntries, bool *hasTransparency, int *colorType,
+                          png_bytepp outRows)
+{
+    int w = imageInfo.width;
+    int h = imageInfo.height;
+    int i, j, rr, gg, bb, aa, idx;
+    uint32_t colors[256], col;
+    int num_colors = 0;
+    int maxGrayDeviation = 0;
+
+    bool isOpaque = true;
+    bool isPalette = true;
+    bool isGrayscale = true;
+
+    // Scan the entire image and determine if:
+    // 1. Every pixel has R == G == B (grayscale)
+    // 2. Every pixel has A == 255 (opaque)
+    // 3. There are no more than 256 distinct RGBA colors
+
+    // NOISY(printf("Initial image data:\n"));
+    // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
+
+    for (j = 0; j < h; j++) {
+        png_bytep row = imageInfo.rows[j];
+        png_bytep out = outRows[j];
+        for (i = 0; i < w; i++) {
+            rr = *row++;
+            gg = *row++;
+            bb = *row++;
+            aa = *row++;
+
+            int odev = maxGrayDeviation;
+            maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+            maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+            maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+            if (maxGrayDeviation > odev) {
+                NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+                             maxGrayDeviation, i, j, rr, gg, bb, aa));
+            }
+
+            // Check if image is really grayscale
+            if (isGrayscale) {
+                if (rr != gg || rr != bb) {
+                     NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+                                  i, j, rr, gg, bb, aa));
+                    isGrayscale = false;
+                }
+            }
+
+            // Check if image is really opaque
+            if (isOpaque) {
+                if (aa != 0xff) {
+                    NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+                                 i, j, rr, gg, bb, aa));
+                    isOpaque = false;
+                }
+            }
+
+            // Check if image is really <= 256 colors
+            if (isPalette) {
+                col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
+                bool match = false;
+                for (idx = 0; idx < num_colors; idx++) {
+                    if (colors[idx] == col) {
+                        match = true;
+                        break;
+                    }
+                }
+
+                // Write the palette index for the pixel to outRows optimistically
+                // We might overwrite it later if we decide to encode as gray or
+                // gray + alpha
+                *out++ = idx;
+                if (!match) {
+                    if (num_colors == 256) {
+                        NOISY(printf("Found 257th color at %d, %d\n", i, j));
+                        isPalette = false;
+                    } else {
+                        colors[num_colors++] = col;
+                    }
+                }
+            }
+        }
+    }
+
+    *paletteEntries = 0;
+    *hasTransparency = !isOpaque;
+    int bpp = isOpaque ? 3 : 4;
+    int paletteSize = w * h + bpp * num_colors;
+
+    NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"));
+    NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false"));
+    NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false"));
+    NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+                 paletteSize, 2 * w * h, bpp * w * h));
+    NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance));
+
+    // Choose the best color type for the image.
+    // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+    // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+    //     is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+    // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+    //     small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+    if (isGrayscale) {
+        if (isOpaque) {
+            *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+        } else {
+            // Use a simple heuristic to determine whether using a palette will
+            // save space versus using gray + alpha for each pixel.
+            // This doesn't take into account chunk overhead, filtering, LZ
+            // compression, etc.
+            if (isPalette && (paletteSize < 2 * w * h)) {
+                *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+            } else {
+                *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+            }
+        }
+    } else if (isPalette && (paletteSize < bpp * w * h)) {
+        *colorType = PNG_COLOR_TYPE_PALETTE;
+    } else {
+        if (maxGrayDeviation <= grayscaleTolerance) {
+            printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation);
+            *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+        } else {
+            *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+        }
+    }
+
+    // Perform postprocessing of the image or palette data based on the final
+    // color type chosen
+
+    if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+        // Create separate RGB and Alpha palettes and set the number of colors
+        *paletteEntries = num_colors;
+
+        // Create the RGB and alpha palettes
+        for (int idx = 0; idx < num_colors; idx++) {
+            col = colors[idx];
+            rgbPalette[idx].red   = (png_byte) ((col >> 24) & 0xff);
+            rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
+            rgbPalette[idx].blue  = (png_byte) ((col >>  8) & 0xff);
+            alphaPalette[idx]     = (png_byte)  (col        & 0xff);
+        }
+    } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        // If the image is gray or gray + alpha, compact the pixels into outRows
+        for (j = 0; j < h; j++) {
+            png_bytep row = imageInfo.rows[j];
+            png_bytep out = outRows[j];
+            for (i = 0; i < w; i++) {
+                rr = *row++;
+                gg = *row++;
+                bb = *row++;
+                aa = *row++;
+                
+                if (isGrayscale) {
+                    *out++ = rr;
+                } else {
+                    *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+                }
+                if (!isOpaque) {
+                    *out++ = aa;
+                }
+           }
+        }
+    }
+}
+
+
+static void write_png(const char* imageName,
+                      png_structp write_ptr, png_infop write_info,
+                      image_info& imageInfo, int grayscaleTolerance)
+{
+    bool optimize = true;
+    png_uint_32 width, height;
+    int color_type;
+    int bit_depth, interlace_type, compression_type;
+    int i;
+
+    png_unknown_chunk unknowns[2];
+    unknowns[0].data = NULL;
+    unknowns[1].data = NULL;
+
+    png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
+    if (outRows == (png_bytepp) 0) {
+        printf("Can't allocate output buffer!\n");
+        exit(1);
+    }
+    for (i = 0; i < (int) imageInfo.height; i++) {
+        outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
+        if (outRows[i] == (png_bytep) 0) {
+            printf("Can't allocate output buffer!\n");
+            exit(1);
+        }
+    }
+
+    png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+
+    NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
+          (int) imageInfo.width, (int) imageInfo.height));
+
+    png_color rgbPalette[256];
+    png_byte alphaPalette[256];
+    bool hasTransparency;
+    int paletteEntries;
+
+    analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
+                  &paletteEntries, &hasTransparency, &color_type, outRows);
+
+    // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+    // sure the pixels will not be pre-dithered/clamped until we decide they are
+    if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
+            color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
+        color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+    }
+
+    switch (color_type) {
+    case PNG_COLOR_TYPE_PALETTE:
+        NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
+                     imageName, paletteEntries,
+                     hasTransparency ? " (with alpha)" : ""));
+        break;
+    case PNG_COLOR_TYPE_GRAY:
+        NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
+        break;
+    case PNG_COLOR_TYPE_GRAY_ALPHA:
+        NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
+        break;
+    case PNG_COLOR_TYPE_RGB:
+        NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
+        break;
+    case PNG_COLOR_TYPE_RGB_ALPHA:
+        NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
+        break;
+    }
+
+    png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
+                 8, color_type, PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    if (color_type == PNG_COLOR_TYPE_PALETTE) {
+        png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
+        if (hasTransparency) {
+            png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
+        }
+       png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
+    } else {
+       png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
+    }
+
+    if (imageInfo.is9Patch) {
+        int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0);
+        int p_index = imageInfo.haveLayoutBounds ? 1 : 0;
+        int b_index = 0;
+        png_byte *chunk_names = imageInfo.haveLayoutBounds
+                ? (png_byte*)"npLb\0npTc\0"
+                : (png_byte*)"npTc";
+        NOISY(printf("Adding 9-patch info...\n"));
+        strcpy((char*)unknowns[p_index].name, "npTc");
+        unknowns[p_index].data = (png_byte*)imageInfo.info9Patch.serialize();
+        unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
+        // TODO: remove the check below when everything works
+        checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
+
+        if (imageInfo.haveLayoutBounds) {
+            int chunk_size = sizeof(png_uint_32) * 4;
+            strcpy((char*)unknowns[b_index].name, "npLb");
+            unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
+            memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
+            unknowns[b_index].size = chunk_size;
+        }
+
+        for (int i = 0; i < chunk_count; i++) {
+            unknowns[i].location = PNG_HAVE_PLTE;
+        }
+        png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
+                                    chunk_names, chunk_count);
+        png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
+#if PNG_LIBPNG_VER < 10600
+        /* Deal with unknown chunk location bug in 1.5.x and earlier */
+        png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
+        if (imageInfo.haveLayoutBounds) {
+            png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
+        }
+#endif
+    }
+
+
+    png_write_info(write_ptr, write_info);
+
+    png_bytepp rows;
+    if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+        if (color_type == PNG_COLOR_TYPE_RGB) {
+            png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+        }
+        rows = imageInfo.rows;
+    } else {
+        rows = outRows;
+    }
+    png_write_image(write_ptr, rows);
+
+//     NOISY(printf("Final image data:\n"));
+//     dump_image(imageInfo.width, imageInfo.height, rows, color_type);
+
+    png_write_end(write_ptr, write_info);
+
+    for (i = 0; i < (int) imageInfo.height; i++) {
+        free(outRows[i]);
+    }
+    free(outRows);
+    free(unknowns[0].data);
+    free(unknowns[1].data);
+
+    png_get_IHDR(write_ptr, write_info, &width, &height,
+       &bit_depth, &color_type, &interlace_type,
+       &compression_type, NULL);
+
+    NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
+                 (int)width, (int)height, bit_depth, color_type, interlace_type,
+                 compression_type));
+}
+
+status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
+                         const sp<AaptFile>& file, String8* outNewLeafName)
+{
+    String8 ext(file->getPath().getPathExtension());
+
+    // We currently only process PNG images.
+    if (strcmp(ext.string(), ".png") != 0) {
+        return NO_ERROR;
+    }
+
+    // Example of renaming a file:
+    //*outNewLeafName = file->getPath().getBasePath().getFileName();
+    //outNewLeafName->append(".nupng");
+
+    String8 printableName(file->getPrintableSource());
+
+    if (bundle->getVerbose()) {
+        printf("Processing image: %s\n", printableName.string());
+    }
+
+    png_structp read_ptr = NULL;
+    png_infop read_info = NULL;
+    FILE* fp;
+
+    image_info imageInfo;
+
+    png_structp write_ptr = NULL;
+    png_infop write_info = NULL;
+
+    status_t error = UNKNOWN_ERROR;
+
+    const size_t nameLen = file->getPath().length();
+
+    fp = fopen(file->getSourceFile().string(), "rb");
+    if (fp == NULL) {
+        fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
+        goto bail;
+    }
+
+    read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
+                                        (png_error_ptr)NULL);
+    if (!read_ptr) {
+        goto bail;
+    }
+
+    read_info = png_create_info_struct(read_ptr);
+    if (!read_info) {
+        goto bail;
+    }
+
+    if (setjmp(png_jmpbuf(read_ptr))) {
+        goto bail;
+    }
+
+    png_init_io(read_ptr, fp);
+
+    read_png(printableName.string(), read_ptr, read_info, &imageInfo);
+
+    if (nameLen > 6) {
+        const char* name = file->getPath().string();
+        if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
+            if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
+                goto bail;
+            }
+        }
+    }
+
+    write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
+                                        (png_error_ptr)NULL);
+    if (!write_ptr)
+    {
+        goto bail;
+    }
+
+    write_info = png_create_info_struct(write_ptr);
+    if (!write_info)
+    {
+        goto bail;
+    }
+
+    png_set_write_fn(write_ptr, (void*)file.get(),
+                     png_write_aapt_file, png_flush_aapt_file);
+
+    if (setjmp(png_jmpbuf(write_ptr)))
+    {
+        goto bail;
+    }
+
+    write_png(printableName.string(), write_ptr, write_info, imageInfo,
+              bundle->getGrayscaleTolerance());
+
+    error = NO_ERROR;
+
+    if (bundle->getVerbose()) {
+        fseek(fp, 0, SEEK_END);
+        size_t oldSize = (size_t)ftell(fp);
+        size_t newSize = file->getSize();
+        float factor = ((float)newSize)/oldSize;
+        int percent = (int)(factor*100);
+        printf("    (processed image %s: %d%% size of source)\n", printableName.string(), percent);
+    }
+
+bail:
+    if (read_ptr) {
+        png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
+    }
+    if (fp) {
+        fclose(fp);
+    }
+    if (write_ptr) {
+        png_destroy_write_struct(&write_ptr, &write_info);
+    }
+
+    if (error != NO_ERROR) {
+        fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
+                file->getPrintableSource().string());
+    }
+    return error;
+}
+
+status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest)
+{
+    png_structp read_ptr = NULL;
+    png_infop read_info = NULL;
+
+    FILE* fp;
+
+    image_info imageInfo;
+
+    png_structp write_ptr = NULL;
+    png_infop write_info = NULL;
+
+    status_t error = UNKNOWN_ERROR;
+
+    if (bundle->getVerbose()) {
+        printf("Processing image to cache: %s => %s\n", source.string(), dest.string());
+    }
+
+    // Get a file handler to read from
+    fp = fopen(source.string(),"rb");
+    if (fp == NULL) {
+        fprintf(stderr, "%s ERROR: Unable to open PNG file\n", source.string());
+        return error;
+    }
+
+    // Call libpng to get a struct to read image data into
+    read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!read_ptr) {
+        fclose(fp);
+        png_destroy_read_struct(&read_ptr, &read_info,NULL);
+        return error;
+    }
+
+    // Call libpng to get a struct to read image info into
+    read_info = png_create_info_struct(read_ptr);
+    if (!read_info) {
+        fclose(fp);
+        png_destroy_read_struct(&read_ptr, &read_info,NULL);
+        return error;
+    }
+
+    // Set a jump point for libpng to long jump back to on error
+    if (setjmp(png_jmpbuf(read_ptr))) {
+        fclose(fp);
+        png_destroy_read_struct(&read_ptr, &read_info,NULL);
+        return error;
+    }
+
+    // Set up libpng to read from our file.
+    png_init_io(read_ptr,fp);
+
+    // Actually read data from the file
+    read_png(source.string(), read_ptr, read_info, &imageInfo);
+
+    // We're done reading so we can clean up
+    // Find old file size before releasing handle
+    fseek(fp, 0, SEEK_END);
+    size_t oldSize = (size_t)ftell(fp);
+    fclose(fp);
+    png_destroy_read_struct(&read_ptr, &read_info,NULL);
+
+    // Check to see if we're dealing with a 9-patch
+    // If we are, process appropriately
+    if (source.getBasePath().getPathExtension() == ".9")  {
+        if (do_9patch(source.string(), &imageInfo) != NO_ERROR) {
+            return error;
+        }
+    }
+
+    // Call libpng to create a structure to hold the processed image data
+    // that can be written to disk
+    write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!write_ptr) {
+        png_destroy_write_struct(&write_ptr, &write_info);
+        return error;
+    }
+
+    // Call libpng to create a structure to hold processed image info that can
+    // be written to disk
+    write_info = png_create_info_struct(write_ptr);
+    if (!write_info) {
+        png_destroy_write_struct(&write_ptr, &write_info);
+        return error;
+    }
+
+    // Open up our destination file for writing
+    fp = fopen(dest.string(), "wb");
+    if (!fp) {
+        fprintf(stderr, "%s ERROR: Unable to open PNG file\n", dest.string());
+        png_destroy_write_struct(&write_ptr, &write_info);
+        return error;
+    }
+
+    // Set up libpng to write to our file
+    png_init_io(write_ptr, fp);
+
+    // Set up a jump for libpng to long jump back on on errors
+    if (setjmp(png_jmpbuf(write_ptr))) {
+        fclose(fp);
+        png_destroy_write_struct(&write_ptr, &write_info);
+        return error;
+    }
+
+    // Actually write out to the new png
+    write_png(dest.string(), write_ptr, write_info, imageInfo,
+              bundle->getGrayscaleTolerance());
+
+    if (bundle->getVerbose()) {
+        // Find the size of our new file
+        FILE* reader = fopen(dest.string(), "rb");
+        fseek(reader, 0, SEEK_END);
+        size_t newSize = (size_t)ftell(reader);
+        fclose(reader);
+
+        float factor = ((float)newSize)/oldSize;
+        int percent = (int)(factor*100);
+        printf("  (processed image to cache entry %s: %d%% size of source)\n",
+               dest.string(), percent);
+    }
+
+    //Clean up
+    fclose(fp);
+    png_destroy_write_struct(&write_ptr, &write_info);
+
+    return NO_ERROR;
+}
+
+status_t postProcessImage(const sp<AaptAssets>& assets,
+                          ResourceTable* table, const sp<AaptFile>& file)
+{
+    String8 ext(file->getPath().getPathExtension());
+
+    // At this point, now that we have all the resource data, all we need to
+    // do is compile XML files.
+    if (strcmp(ext.string(), ".xml") == 0) {
+        return compileXmlFile(assets, file, table);
+    }
+
+    return NO_ERROR;
+}
diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h
new file mode 100644
index 0000000..91b6554
--- /dev/null
+++ b/tools/aapt/Images.h
@@ -0,0 +1,26 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef IMAGES_H
+#define IMAGES_H
+
+#include "ResourceTable.h"
+#include "Bundle.h"
+
+#include <utils/String8.h>
+#include <utils/RefBase.h>
+
+using android::String8;
+
+status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
+                         const sp<AaptFile>& file, String8* outNewLeafName);
+
+status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest);
+
+status_t postProcessImage(const sp<AaptAssets>& assets,
+                          ResourceTable* table, const sp<AaptFile>& file);
+
+#endif
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
new file mode 100644
index 0000000..4a8aa9c
--- /dev/null
+++ b/tools/aapt/Main.cpp
@@ -0,0 +1,655 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Android Asset Packaging Tool main entry point.
+//
+#include "Main.h"
+#include "Bundle.h"
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <assert.h>
+
+using namespace android;
+
+static const char* gProgName = "aapt";
+
+/*
+ * When running under Cygwin on Windows, this will convert slash-based
+ * paths into back-slash-based ones. Otherwise the ApptAssets file comparisons
+ * fail later as they use back-slash separators under Windows.
+ *
+ * This operates in-place on the path string.
+ */
+void convertPath(char *path) {
+  if (path != NULL && OS_PATH_SEPARATOR != '/') {
+    for (; *path; path++) {
+      if (*path == '/') {
+        *path = OS_PATH_SEPARATOR;
+      }
+    }
+  }
+}
+
+/*
+ * Print usage info.
+ */
+void usage(void)
+{
+    fprintf(stderr, "Android Asset Packaging Tool\n\n");
+    fprintf(stderr, "Usage:\n");
+    fprintf(stderr,
+        " %s l[ist] [-v] [-a] file.{zip,jar,apk}\n"
+        "   List contents of Zip-compatible archive.\n\n", gProgName);
+    fprintf(stderr,
+        " %s d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]\n"
+        "   strings          Print the contents of the resource table string pool in the APK.\n"
+        "   badging          Print the label and icon for the app declared in APK.\n"
+        "   permissions      Print the permissions from the APK.\n"
+        "   resources        Print the resource table from the APK.\n"
+        "   configurations   Print the configurations in the APK.\n"
+        "   xmltree          Print the compiled xmls in the given assets.\n"
+        "   xmlstrings       Print the strings of the given compiled xml assets.\n\n", gProgName);
+    fprintf(stderr,
+        " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n"
+        "        [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n"
+        "        [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n"
+        "        [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n"
+        "        [--rename-manifest-package PACKAGE] \\\n"
+        "        [--rename-instrumentation-target-package PACKAGE] \\\n"
+        "        [--utf16] [--auto-add-overlay] \\\n"
+        "        [--max-res-version VAL] \\\n"
+        "        [-I base-package [-I base-package ...]] \\\n"
+        "        [-A asset-source-dir]  [-G class-list-file] [-P public-definitions-file] \\\n"
+        "        [-S resource-sources [-S resource-sources ...]] \\\n"
+        "        [-F apk-file] [-J R-file-dir] \\\n"
+        "        [--product product1,product2,...] \\\n"
+        "        [-c CONFIGS] [--preferred-configurations CONFIGS] \\\n"
+        "        [raw-files-dir [raw-files-dir] ...] \\\n"
+        "        [--output-text-symbols DIR]\n"
+        "\n"
+        "   Package the android resources.  It will read assets and resources that are\n"
+        "   supplied with the -M -A -S or raw-files-dir arguments.  The -J -P -F and -R\n"
+        "   options control which files are output.\n\n"
+        , gProgName);
+    fprintf(stderr,
+        " %s r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]\n"
+        "   Delete specified files from Zip-compatible archive.\n\n",
+        gProgName);
+    fprintf(stderr,
+        " %s a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]\n"
+        "   Add specified files to Zip-compatible archive.\n\n", gProgName);
+    fprintf(stderr,
+        " %s c[runch] [-v] -S resource-sources ... -C output-folder ...\n"
+        "   Do PNG preprocessing on one or several resource folders\n"
+        "   and store the results in the output folder.\n\n", gProgName);
+    fprintf(stderr,
+        " %s s[ingleCrunch] [-v] -i input-file -o outputfile\n"
+        "   Do PNG preprocessing on a single file.\n\n", gProgName);
+    fprintf(stderr,
+        " %s v[ersion]\n"
+        "   Print program version.\n\n", gProgName);
+    fprintf(stderr,
+        " Modifiers:\n"
+        "   -a  print Android-specific data (resources, manifest) when listing\n"
+        "   -c  specify which configurations to include.  The default is all\n"
+        "       configurations.  The value of the parameter should be a comma\n"
+        "       separated list of configuration values.  Locales should be specified\n"
+        "       as either a language or language-region pair.  Some examples:\n"
+        "            en\n"
+        "            port,en\n"
+        "            port,land,en_US\n"
+        "       If you put the special locale, zz_ZZ on the list, it will perform\n"
+        "       pseudolocalization on the default locale, modifying all of the\n"
+        "       strings so you can look for strings that missed the\n"
+        "       internationalization process.  For example:\n"
+        "            port,land,zz_ZZ\n"
+        "   -d  one or more device assets to include, separated by commas\n"
+        "   -f  force overwrite of existing files\n"
+        "   -g  specify a pixel tolerance to force images to grayscale, default 0\n"
+        "   -j  specify a jar or zip file containing classes to include\n"
+        "   -k  junk path of file(s) added\n"
+        "   -m  make package directories under location specified by -J\n"
+#if 0
+        "   -p  pseudolocalize the default configuration\n"
+#endif
+        "   -u  update existing packages (add new, replace older, remove deleted files)\n"
+        "   -v  verbose output\n"
+        "   -x  create extending (non-application) resource IDs\n"
+        "   -z  require localization of resource attributes marked with\n"
+        "       localization=\"suggested\"\n"
+        "   -A  additional directory in which to find raw asset files\n"
+        "   -G  A file to output proguard options into.\n"
+        "   -F  specify the apk file to output\n"
+        "   -I  add an existing package to base include set\n"
+        "   -J  specify where to output R.java resource constant definitions\n"
+        "   -M  specify full path to AndroidManifest.xml to include in zip\n"
+        "   -P  specify where to output public resource definitions\n"
+        "   -S  directory in which to find resources.  Multiple directories will be scanned\n"
+        "       and the first match found (left to right) will take precedence.\n"
+        "   -0  specifies an additional extension for which such files will not\n"
+        "       be stored compressed in the .apk.  An empty string means to not\n"
+        "       compress any files at all.\n"
+        "   --debug-mode\n"
+        "       inserts android:debuggable=\"true\" in to the application node of the\n"
+        "       manifest, making the application debuggable even on production devices.\n"
+        "   --include-meta-data\n"
+        "       when used with \"dump badging\" also includes meta-data tags.\n"
+        "   --min-sdk-version\n"
+        "       inserts android:minSdkVersion in to manifest.  If the version is 7 or\n"
+        "       higher, the default encoding for resources will be in UTF-8.\n"
+        "   --target-sdk-version\n"
+        "       inserts android:targetSdkVersion in to manifest.\n"
+        "   --max-res-version\n"
+        "       ignores versioned resource directories above the given value.\n"
+        "   --values\n"
+        "       when used with \"dump resources\" also includes resource values.\n"
+        "   --version-code\n"
+        "       inserts android:versionCode in to manifest.\n"
+        "   --version-name\n"
+        "       inserts android:versionName in to manifest.\n"
+        "   --custom-package\n"
+        "       generates R.java into a different package.\n"
+        "   --extra-packages\n"
+        "       generate R.java for libraries. Separate libraries with ':'.\n"
+        "   --generate-dependencies\n"
+        "       generate dependency files in the same directories for R.java and resource package\n"
+        "   --auto-add-overlay\n"
+        "       Automatically add resources that are only in overlays.\n"
+        "   --preferred-configurations\n"
+        "       Like the -c option for filtering out unneeded configurations, but\n"
+        "       only expresses a preference.  If there is no resource available with\n"
+        "       the preferred configuration then it will not be stripped.\n"
+        "   --rename-manifest-package\n"
+        "       Rewrite the manifest so that its package name is the package name\n"
+        "       given here.  Relative class names (for example .Foo) will be\n"
+        "       changed to absolute names with the old package so that the code\n"
+        "       does not need to change.\n"
+        "   --rename-instrumentation-target-package\n"
+        "       Rewrite the manifest so that all of its instrumentation\n"
+        "       components target the given package.  Useful when used in\n"
+        "       conjunction with --rename-manifest-package to fix tests against\n"
+        "       a package that has been renamed.\n"
+        "   --product\n"
+        "       Specifies which variant to choose for strings that have\n"
+        "       product variants\n"
+        "   --utf16\n"
+        "       changes default encoding for resources to UTF-16.  Only useful when API\n"
+        "       level is set to 7 or higher where the default encoding is UTF-8.\n"
+        "   --non-constant-id\n"
+        "       Make the resources ID non constant. This is required to make an R java class\n"
+        "       that does not contain the final value but is used to make reusable compiled\n"
+        "       libraries that need to access resources.\n"
+        "   --error-on-failed-insert\n"
+        "       Forces aapt to return an error if it fails to insert values into the manifest\n"
+        "       with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n"
+        "       and --version-name.\n"
+        "       Insertion typically fails if the manifest already defines the attribute.\n"
+        "   --output-text-symbols\n"
+        "       Generates a text file containing the resource symbols of the R class in the\n"
+        "       specified folder.\n"
+        "   --ignore-assets\n"
+        "       Assets to be ignored. Default pattern is:\n"
+        "       %s\n",
+        gDefaultIgnoreAssets);
+}
+
+/*
+ * Dispatch the command.
+ */
+int handleCommand(Bundle* bundle)
+{
+    //printf("--- command %d (verbose=%d force=%d):\n",
+    //    bundle->getCommand(), bundle->getVerbose(), bundle->getForce());
+    //for (int i = 0; i < bundle->getFileSpecCount(); i++)
+    //    printf("  %d: '%s'\n", i, bundle->getFileSpecEntry(i));
+
+    switch (bundle->getCommand()) {
+    case kCommandVersion:      return doVersion(bundle);
+    case kCommandList:         return doList(bundle);
+    case kCommandDump:         return doDump(bundle);
+    case kCommandAdd:          return doAdd(bundle);
+    case kCommandRemove:       return doRemove(bundle);
+    case kCommandPackage:      return doPackage(bundle);
+    case kCommandCrunch:       return doCrunch(bundle);
+    case kCommandSingleCrunch: return doSingleCrunch(bundle);
+    default:
+        fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
+        return 1;
+    }
+}
+
+/*
+ * Parse args.
+ */
+int main(int argc, char* const argv[])
+{
+    char *prog = argv[0];
+    Bundle bundle;
+    bool wantUsage = false;
+    int result = 1;    // pessimistically assume an error.
+    int tolerance = 0;
+
+    /* default to compression */
+    bundle.setCompressionMethod(ZipEntry::kCompressDeflated);
+
+    if (argc < 2) {
+        wantUsage = true;
+        goto bail;
+    }
+
+    if (argv[1][0] == 'v')
+        bundle.setCommand(kCommandVersion);
+    else if (argv[1][0] == 'd')
+        bundle.setCommand(kCommandDump);
+    else if (argv[1][0] == 'l')
+        bundle.setCommand(kCommandList);
+    else if (argv[1][0] == 'a')
+        bundle.setCommand(kCommandAdd);
+    else if (argv[1][0] == 'r')
+        bundle.setCommand(kCommandRemove);
+    else if (argv[1][0] == 'p')
+        bundle.setCommand(kCommandPackage);
+    else if (argv[1][0] == 'c')
+        bundle.setCommand(kCommandCrunch);
+    else if (argv[1][0] == 's')
+        bundle.setCommand(kCommandSingleCrunch);
+    else {
+        fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
+        wantUsage = true;
+        goto bail;
+    }
+    argc -= 2;
+    argv += 2;
+
+    /*
+     * Pull out flags.  We support "-fv" and "-f -v".
+     */
+    while (argc && argv[0][0] == '-') {
+        /* flag(s) found */
+        const char* cp = argv[0] +1;
+
+        while (*cp != '\0') {
+            switch (*cp) {
+            case 'v':
+                bundle.setVerbose(true);
+                break;
+            case 'a':
+                bundle.setAndroidList(true);
+                break;
+            case 'c':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-c' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                bundle.addConfigurations(argv[0]);
+                break;
+            case 'f':
+                bundle.setForce(true);
+                break;
+            case 'g':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-g' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                tolerance = atoi(argv[0]);
+                bundle.setGrayscaleTolerance(tolerance);
+                printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance);
+                break;
+            case 'k':
+                bundle.setJunkPath(true);
+                break;
+            case 'm':
+                bundle.setMakePackageDirs(true);
+                break;
+#if 0
+            case 'p':
+                bundle.setPseudolocalize(true);
+                break;
+#endif
+            case 'u':
+                bundle.setUpdate(true);
+                break;
+            case 'x':
+                bundle.setExtending(true);
+                break;
+            case 'z':
+                bundle.setRequireLocalization(true);
+                break;
+            case 'j':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-j' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.addJarFile(argv[0]);
+                break;
+            case 'A':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-A' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setAssetSourceDir(argv[0]);
+                break;
+            case 'G':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-G' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setProguardFile(argv[0]);
+                break;
+            case 'I':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-I' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.addPackageInclude(argv[0]);
+                break;
+            case 'F':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-F' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setOutputAPKFile(argv[0]);
+                break;
+            case 'J':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-J' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setRClassDir(argv[0]);
+                break;
+            case 'M':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setAndroidManifestFile(argv[0]);
+                break;
+            case 'P':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-P' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setPublicOutputFile(argv[0]);
+                break;
+            case 'S':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-S' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.addResourceSourceDir(argv[0]);
+                break;
+            case 'C':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-C' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setCrunchedOutputDir(argv[0]);
+                break;
+            case 'i':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-i' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setSingleCrunchInputFile(argv[0]);
+                break;
+            case 'o':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-o' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                convertPath(argv[0]);
+                bundle.setSingleCrunchOutputFile(argv[0]);
+                break;
+            case '0':
+                argc--;
+                argv++;
+                if (!argc) {
+                    fprintf(stderr, "ERROR: No argument supplied for '-e' option\n");
+                    wantUsage = true;
+                    goto bail;
+                }
+                if (argv[0][0] != 0) {
+                    bundle.addNoCompressExtension(argv[0]);
+                } else {
+                    bundle.setCompressionMethod(ZipEntry::kCompressStored);
+                }
+                break;
+            case '-':
+                if (strcmp(cp, "-debug-mode") == 0) {
+                    bundle.setDebugMode(true);
+                } else if (strcmp(cp, "-min-sdk-version") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--min-sdk-version' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setMinSdkVersion(argv[0]);
+                } else if (strcmp(cp, "-target-sdk-version") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--target-sdk-version' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setTargetSdkVersion(argv[0]);
+                } else if (strcmp(cp, "-max-sdk-version") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--max-sdk-version' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setMaxSdkVersion(argv[0]);
+                } else if (strcmp(cp, "-max-res-version") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--max-res-version' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setMaxResVersion(argv[0]);
+                } else if (strcmp(cp, "-version-code") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--version-code' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setVersionCode(argv[0]);
+                } else if (strcmp(cp, "-version-name") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--version-name' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setVersionName(argv[0]);
+                } else if (strcmp(cp, "-values") == 0) {
+                    bundle.setValues(true);
+                } else if (strcmp(cp, "-include-meta-data") == 0) {
+                    bundle.setIncludeMetaData(true);
+                } else if (strcmp(cp, "-custom-package") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--custom-package' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setCustomPackage(argv[0]);
+                } else if (strcmp(cp, "-extra-packages") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--extra-packages' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setExtraPackages(argv[0]);
+                } else if (strcmp(cp, "-generate-dependencies") == 0) {
+                    bundle.setGenDependencies(true);
+                } else if (strcmp(cp, "-utf16") == 0) {
+                    bundle.setWantUTF16(true);
+                } else if (strcmp(cp, "-preferred-configurations") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--preferred-configurations' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.addPreferredConfigurations(argv[0]);
+                } else if (strcmp(cp, "-rename-manifest-package") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--rename-manifest-package' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setManifestPackageNameOverride(argv[0]);
+                } else if (strcmp(cp, "-rename-instrumentation-target-package") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--rename-instrumentation-target-package' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setInstrumentationPackageNameOverride(argv[0]);
+                } else if (strcmp(cp, "-auto-add-overlay") == 0) {
+                    bundle.setAutoAddOverlay(true);
+                } else if (strcmp(cp, "-error-on-failed-insert") == 0) {
+                    bundle.setErrorOnFailedInsert(true);
+                } else if (strcmp(cp, "-output-text-symbols") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '-output-text-symbols' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setOutputTextSymbols(argv[0]);
+                } else if (strcmp(cp, "-product") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--product' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.setProduct(argv[0]);
+                } else if (strcmp(cp, "-non-constant-id") == 0) {
+                    bundle.setNonConstantId(true);
+                } else if (strcmp(cp, "-no-crunch") == 0) {
+                    bundle.setUseCrunchCache(true);
+                } else if (strcmp(cp, "-ignore-assets") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--ignore-assets' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    gUserIgnoreAssets = argv[0];
+                } else {
+                    fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp);
+                    wantUsage = true;
+                    goto bail;
+                }
+                cp += strlen(cp) - 1;
+                break;
+            default:
+                fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp);
+                wantUsage = true;
+                goto bail;
+            }
+
+            cp++;
+        }
+        argc--;
+        argv++;
+    }
+
+    /*
+     * We're past the flags.  The rest all goes straight in.
+     */
+    bundle.setFileSpec(argv, argc);
+
+    result = handleCommand(&bundle);
+
+bail:
+    if (wantUsage) {
+        usage();
+        result = 2;
+    }
+
+    //printf("--> returning %d\n", result);
+    return result;
+}
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
new file mode 100644
index 0000000..a6b39ac
--- /dev/null
+++ b/tools/aapt/Main.h
@@ -0,0 +1,63 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Some global defines that don't really merit their own header.
+//
+#ifndef __MAIN_H
+#define __MAIN_H
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include "Bundle.h"
+#include "AaptAssets.h"
+#include "ZipFile.h"
+
+
+/* Benchmarking Flag */
+//#define BENCHMARK 1
+
+#if BENCHMARK
+    #include <time.h>
+#endif /* BENCHMARK */
+
+extern int doVersion(Bundle* bundle);
+extern int doList(Bundle* bundle);
+extern int doDump(Bundle* bundle);
+extern int doAdd(Bundle* bundle);
+extern int doRemove(Bundle* bundle);
+extern int doPackage(Bundle* bundle);
+extern int doCrunch(Bundle* bundle);
+extern int doSingleCrunch(Bundle* bundle);
+
+extern int calcPercent(long uncompressedLen, long compressedLen);
+
+extern android::status_t writeAPK(Bundle* bundle,
+    const sp<AaptAssets>& assets,
+    const android::String8& outputFile);
+
+extern android::status_t updatePreProcessedCache(Bundle* bundle);
+
+extern android::status_t buildResources(Bundle* bundle,
+    const sp<AaptAssets>& assets);
+
+extern android::status_t writeResourceSymbols(Bundle* bundle,
+    const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate);
+
+extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets);
+
+extern bool isValidResourceType(const String8& type);
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
+
+extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets);
+
+int dumpResources(Bundle* bundle);
+
+String8 getAttribute(const ResXMLTree& tree, const char* ns,
+                            const char* attr, String8* outError);
+
+status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets,
+                                FILE* fp, bool includeRaw);
+#endif // __MAIN_H
diff --git a/tools/aapt/NOTICE b/tools/aapt/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/tools/aapt/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
new file mode 100644
index 0000000..872d95c
--- /dev/null
+++ b/tools/aapt/Package.cpp
@@ -0,0 +1,505 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Package assets into Zip files.
+//
+#include "Main.h"
+#include "AaptAssets.h"
+#include "ResourceTable.h"
+#include "ResourceFilter.h"
+
+#include <androidfw/misc.h>
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/misc.h>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <errno.h>
+
+using namespace android;
+
+static const char* kExcludeExtension = ".EXCLUDE";
+
+/* these formats are already compressed, or don't compress well */
+static const char* kNoCompressExt[] = {
+    ".jpg", ".jpeg", ".png", ".gif",
+    ".wav", ".mp2", ".mp3", ".ogg", ".aac",
+    ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
+    ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
+    ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
+    ".amr", ".awb", ".wma", ".wmv"
+};
+
+/* fwd decls, so I can write this downward */
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
+                        const AaptGroupEntry& ge, const ResourceFilter* filter);
+bool processFile(Bundle* bundle, ZipFile* zip,
+                        const sp<AaptGroup>& group, const sp<AaptFile>& file);
+bool okayToCompress(Bundle* bundle, const String8& pathName);
+ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
+
+/*
+ * The directory hierarchy looks like this:
+ * "outputDir" and "assetRoot" are existing directories.
+ *
+ * On success, "bundle->numPackages" will be the number of Zip packages
+ * we created.
+ */
+status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets,
+                       const String8& outputFile)
+{
+    #if BENCHMARK
+    fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
+    long startAPKTime = clock();
+    #endif /* BENCHMARK */
+
+    status_t result = NO_ERROR;
+    ZipFile* zip = NULL;
+    int count;
+
+    //bundle->setPackageCount(0);
+
+    /*
+     * Prep the Zip archive.
+     *
+     * If the file already exists, fail unless "update" or "force" is set.
+     * If "update" is set, update the contents of the existing archive.
+     * Else, if "force" is set, remove the existing archive.
+     */
+    FileType fileType = getFileType(outputFile.string());
+    if (fileType == kFileTypeNonexistent) {
+        // okay, create it below
+    } else if (fileType == kFileTypeRegular) {
+        if (bundle->getUpdate()) {
+            // okay, open it below
+        } else if (bundle->getForce()) {
+            if (unlink(outputFile.string()) != 0) {
+                fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(),
+                        strerror(errno));
+                goto bail;
+            }
+        } else {
+            fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n",
+                    outputFile.string());
+            goto bail;
+        }
+    } else {
+        fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string());
+        goto bail;
+    }
+
+    if (bundle->getVerbose()) {
+        printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening",
+                outputFile.string());
+    }
+
+    status_t status;
+    zip = new ZipFile;
+    status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate);
+    if (status != NO_ERROR) {
+        fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n",
+                outputFile.string());
+        goto bail;
+    }
+
+    if (bundle->getVerbose()) {
+        printf("Writing all files...\n");
+    }
+
+    count = processAssets(bundle, zip, assets);
+    if (count < 0) {
+        fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
+                outputFile.string());
+        result = count;
+        goto bail;
+    }
+
+    if (bundle->getVerbose()) {
+        printf("Generated %d file%s\n", count, (count==1) ? "" : "s");
+    }
+    
+    count = processJarFiles(bundle, zip);
+    if (count < 0) {
+        fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n",
+                outputFile.string());
+        result = count;
+        goto bail;
+    }
+    
+    if (bundle->getVerbose())
+        printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s");
+    
+    result = NO_ERROR;
+
+    /*
+     * Check for cruft.  We set the "marked" flag on all entries we created
+     * or decided not to update.  If the entry isn't already slated for
+     * deletion, remove it now.
+     */
+    {
+        if (bundle->getVerbose())
+            printf("Checking for deleted files\n");
+        int i, removed = 0;
+        for (i = 0; i < zip->getNumEntries(); i++) {
+            ZipEntry* entry = zip->getEntryByIndex(i);
+
+            if (!entry->getMarked() && entry->getDeleted()) {
+                if (bundle->getVerbose()) {
+                    printf("      (removing crufty '%s')\n",
+                        entry->getFileName());
+                }
+                zip->remove(entry);
+                removed++;
+            }
+        }
+        if (bundle->getVerbose() && removed > 0)
+            printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s");
+    }
+
+    /* tell Zip lib to process deletions and other pending changes */
+    result = zip->flush();
+    if (result != NO_ERROR) {
+        fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
+        goto bail;
+    }
+
+    /* anything here? */
+    if (zip->getNumEntries() == 0) {
+        if (bundle->getVerbose()) {
+            printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string());
+        }
+        delete zip;        // close the file so we can remove it in Win32
+        zip = NULL;
+        if (unlink(outputFile.string()) != 0) {
+            fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string());
+        }
+    }
+
+    // If we've been asked to generate a dependency file for the .ap_ package,
+    // do so here
+    if (bundle->getGenDependencies()) {
+        // The dependency file gets output to the same directory
+        // as the specified output file with an additional .d extension.
+        // e.g. bin/resources.ap_.d
+        String8 dependencyFile = outputFile;
+        dependencyFile.append(".d");
+
+        FILE* fp = fopen(dependencyFile.string(), "a");
+        // Add this file to the dependency file
+        fprintf(fp, "%s \\\n", outputFile.string());
+        fclose(fp);
+    }
+
+    assert(result == NO_ERROR);
+
+bail:
+    delete zip;        // must close before remove in Win32
+    if (result != NO_ERROR) {
+        if (bundle->getVerbose()) {
+            printf("Removing %s due to earlier failures\n", outputFile.string());
+        }
+        if (unlink(outputFile.string()) != 0) {
+            fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string());
+        }
+    }
+
+    if (result == NO_ERROR && bundle->getVerbose())
+        printf("Done!\n");
+
+    #if BENCHMARK
+    fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0);
+    #endif /* BENCHMARK */
+    return result;
+}
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip,
+                      const sp<AaptAssets>& assets)
+{
+    ResourceFilter filter;
+    status_t status = filter.parse(bundle->getConfigurations());
+    if (status != NO_ERROR) {
+        return -1;
+    }
+
+    ssize_t count = 0;
+
+    const size_t N = assets->getGroupEntries().size();
+    for (size_t i=0; i<N; i++) {
+        const AaptGroupEntry& ge = assets->getGroupEntries()[i];
+
+        ssize_t res = processAssets(bundle, zip, assets, ge, &filter);
+        if (res < 0) {
+            return res;
+        }
+
+        count += res;
+    }
+
+    return count;
+}
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
+        const AaptGroupEntry& ge, const ResourceFilter* filter)
+{
+    ssize_t count = 0;
+
+    const size_t ND = dir->getDirs().size();
+    size_t i;
+    for (i=0; i<ND; i++) {
+        const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
+
+        const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0;
+
+        if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) {
+            continue;
+        }
+
+        ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL);
+        if (res < 0) {
+            return res;
+        }
+        count += res;
+    }
+
+    if (filter != NULL && !filter->match(ge.toParams())) {
+        return count;
+    }
+
+    const size_t NF = dir->getFiles().size();
+    for (i=0; i<NF; i++) {
+        sp<AaptGroup> gp = dir->getFiles().valueAt(i);
+        ssize_t fi = gp->getFiles().indexOfKey(ge);
+        if (fi >= 0) {
+            sp<AaptFile> fl = gp->getFiles().valueAt(fi);
+            if (!processFile(bundle, zip, gp, fl)) {
+                return UNKNOWN_ERROR;
+            }
+            count++;
+        }
+    }
+
+    return count;
+}
+
+/*
+ * Process a regular file, adding it to the archive if appropriate.
+ *
+ * If we're in "update" mode, and the file already exists in the archive,
+ * delete the existing entry before adding the new one.
+ */
+bool processFile(Bundle* bundle, ZipFile* zip,
+                 const sp<AaptGroup>& group, const sp<AaptFile>& file)
+{
+    const bool hasData = file->hasData();
+
+    String8 storageName(group->getPath());
+    storageName.convertToResPath();
+    ZipEntry* entry;
+    bool fromGzip = false;
+    status_t result;
+
+    /*
+     * See if the filename ends in ".EXCLUDE".  We can't use
+     * String8::getPathExtension() because the length of what it considers
+     * to be an extension is capped.
+     *
+     * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives,
+     * so there's no value in adding them (and it makes life easier on
+     * the AssetManager lib if we don't).
+     *
+     * NOTE: this restriction has been removed.  If you're in this code, you
+     * should clean this up, but I'm in here getting rid of Path Name, and I
+     * don't want to make other potentially breaking changes --joeo
+     */
+    int fileNameLen = storageName.length();
+    int excludeExtensionLen = strlen(kExcludeExtension);
+    if (fileNameLen > excludeExtensionLen
+            && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen),
+                            kExcludeExtension))) {
+        fprintf(stderr, "warning: '%s' not added to Zip\n", storageName.string());
+        return true;
+    }
+
+    if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) {
+        fromGzip = true;
+        storageName = storageName.getBasePath();
+    }
+
+    if (bundle->getUpdate()) {
+        entry = zip->getEntryByName(storageName.string());
+        if (entry != NULL) {
+            /* file already exists in archive; there can be only one */
+            if (entry->getMarked()) {
+                fprintf(stderr,
+                        "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n",
+                        file->getPrintableSource().string());
+                return false;
+            }
+            if (!hasData) {
+                const String8& srcName = file->getSourceFile();
+                time_t fileModWhen;
+                fileModWhen = getFileModDate(srcName.string());
+                if (fileModWhen == (time_t) -1) { // file existence tested earlier,
+                    return false;                 //  not expecting an error here
+                }
+    
+                if (fileModWhen > entry->getModWhen()) {
+                    // mark as deleted so add() will succeed
+                    if (bundle->getVerbose()) {
+                        printf("      (removing old '%s')\n", storageName.string());
+                    }
+    
+                    zip->remove(entry);
+                } else {
+                    // version in archive is newer
+                    if (bundle->getVerbose()) {
+                        printf("      (not updating '%s')\n", storageName.string());
+                    }
+                    entry->setMarked(true);
+                    return true;
+                }
+            } else {
+                // Generated files are always replaced.
+                zip->remove(entry);
+            }
+        }
+    }
+
+    //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE);
+
+    if (fromGzip) {
+        result = zip->addGzip(file->getSourceFile().string(), storageName.string(), &entry);
+    } else if (!hasData) {
+        /* don't compress certain files, e.g. PNGs */
+        int compressionMethod = bundle->getCompressionMethod();
+        if (!okayToCompress(bundle, storageName)) {
+            compressionMethod = ZipEntry::kCompressStored;
+        }
+        result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod,
+                            &entry);
+    } else {
+        result = zip->add(file->getData(), file->getSize(), storageName.string(),
+                           file->getCompressionMethod(), &entry);
+    }
+    if (result == NO_ERROR) {
+        if (bundle->getVerbose()) {
+            printf("      '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : "");
+            if (entry->getCompressionMethod() == ZipEntry::kCompressStored) {
+                printf(" (not compressed)\n");
+            } else {
+                printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(),
+                            entry->getCompressedLen()));
+            }
+        }
+        entry->setMarked(true);
+    } else {
+        if (result == ALREADY_EXISTS) {
+            fprintf(stderr, "      Unable to add '%s': file already in archive (try '-u'?)\n",
+                    file->getPrintableSource().string());
+        } else {
+            fprintf(stderr, "      Unable to add '%s': Zip add failed\n", 
+                    file->getPrintableSource().string());
+        }
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * Determine whether or not we want to try to compress this file based
+ * on the file extension.
+ */
+bool okayToCompress(Bundle* bundle, const String8& pathName)
+{
+    String8 ext = pathName.getPathExtension();
+    int i;
+
+    if (ext.length() == 0)
+        return true;
+
+    for (i = 0; i < NELEM(kNoCompressExt); i++) {
+        if (strcasecmp(ext.string(), kNoCompressExt[i]) == 0)
+            return false;
+    }
+
+    const android::Vector<const char*>& others(bundle->getNoCompressExtensions());
+    for (i = 0; i < (int)others.size(); i++) {
+        const char* str = others[i];
+        int pos = pathName.length() - strlen(str);
+        if (pos < 0) {
+            continue;
+        }
+        const char* path = pathName.string();
+        if (strcasecmp(path + pos, str) == 0) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool endsWith(const char* haystack, const char* needle)
+{
+    size_t a = strlen(haystack);
+    size_t b = strlen(needle);
+    if (a < b) return false;
+    return strcasecmp(haystack+(a-b), needle) == 0;
+}
+
+ssize_t processJarFile(ZipFile* jar, ZipFile* out)
+{
+    status_t err;
+    size_t N = jar->getNumEntries();
+    size_t count = 0;
+    for (size_t i=0; i<N; i++) {
+        ZipEntry* entry = jar->getEntryByIndex(i);
+        const char* storageName = entry->getFileName();
+        if (endsWith(storageName, ".class")) {
+            int compressionMethod = entry->getCompressionMethod();
+            size_t size = entry->getUncompressedLen();
+            const void* data = jar->uncompress(entry);
+            if (data == NULL) {
+                fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n",
+                    storageName);
+                return -1;
+            }
+            out->add(data, size, storageName, compressionMethod, NULL);
+            free((void*)data);
+        }
+        count++;
+    }
+    return count;
+}
+
+ssize_t processJarFiles(Bundle* bundle, ZipFile* zip)
+{
+    status_t err;
+    ssize_t count = 0;
+    const android::Vector<const char*>& jars = bundle->getJarFiles();
+
+    size_t N = jars.size();
+    for (size_t i=0; i<N; i++) {
+        ZipFile jar;
+        err = jar.open(jars[i], ZipFile::kOpenReadOnly);
+        if (err != 0) {
+            fprintf(stderr, "ERROR: unable to open '%s' as a zip file: %d\n",
+                jars[i], err);
+            return err;
+        }
+        err += processJarFile(&jar, zip);
+        if (err < 0) {
+            fprintf(stderr, "ERROR: unable to process '%s'\n", jars[i]);
+            return err;
+        }
+        count += err;
+    }
+
+    return count;
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
new file mode 100644
index 0000000..d617928
--- /dev/null
+++ b/tools/aapt/Resource.cpp
@@ -0,0 +1,2676 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+#include "Main.h"
+#include "AaptAssets.h"
+#include "StringPool.h"
+#include "XMLNode.h"
+#include "ResourceTable.h"
+#include "Images.h"
+
+#include "CrunchCache.h"
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+
+#include "WorkQueue.h"
+
+#if HAVE_PRINTF_ZD
+#  define ZD "%zd"
+#  define ZD_TYPE ssize_t
+#else
+#  define ZD "%ld"
+#  define ZD_TYPE long
+#endif
+
+#define NOISY(x) // x
+
+// Number of threads to use for preprocessing images.
+static const size_t MAX_THREADS = 4;
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+class PackageInfo
+{
+public:
+    PackageInfo()
+    {
+    }
+    ~PackageInfo()
+    {
+    }
+
+    status_t parsePackage(const sp<AaptGroup>& grp);
+};
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+static String8 parseResourceName(const String8& leaf)
+{
+    const char* firstDot = strchr(leaf.string(), '.');
+    const char* str = leaf.string();
+
+    if (firstDot) {
+        return String8(str, firstDot-str);
+    } else {
+        return String8(str);
+    }
+}
+
+ResourceTypeSet::ResourceTypeSet()
+    :RefBase(),
+     KeyedVector<String8,sp<AaptGroup> >()
+{
+}
+
+FilePathStore::FilePathStore()
+    :RefBase(),
+     Vector<String8>()
+{
+}
+
+class ResourceDirIterator
+{
+public:
+    ResourceDirIterator(const sp<ResourceTypeSet>& set, const String8& resType)
+        : mResType(resType), mSet(set), mSetPos(0), mGroupPos(0)
+    {
+    }
+
+    inline const sp<AaptGroup>& getGroup() const { return mGroup; }
+    inline const sp<AaptFile>& getFile() const { return mFile; }
+
+    inline const String8& getBaseName() const { return mBaseName; }
+    inline const String8& getLeafName() const { return mLeafName; }
+    inline String8 getPath() const { return mPath; }
+    inline const ResTable_config& getParams() const { return mParams; }
+
+    enum {
+        EOD = 1
+    };
+
+    ssize_t next()
+    {
+        while (true) {
+            sp<AaptGroup> group;
+            sp<AaptFile> file;
+
+            // Try to get next file in this current group.
+            if (mGroup != NULL && mGroupPos < mGroup->getFiles().size()) {
+                group = mGroup;
+                file = group->getFiles().valueAt(mGroupPos++);
+
+            // Try to get the next group/file in this directory
+            } else if (mSetPos < mSet->size()) {
+                mGroup = group = mSet->valueAt(mSetPos++);
+                if (group->getFiles().size() < 1) {
+                    continue;
+                }
+                file = group->getFiles().valueAt(0);
+                mGroupPos = 1;
+
+            // All done!
+            } else {
+                return EOD;
+            }
+
+            mFile = file;
+
+            String8 leaf(group->getLeaf());
+            mLeafName = String8(leaf);
+            mParams = file->getGroupEntry().toParams();
+            NOISY(printf("Dir %s: mcc=%d mnc=%d lang=%c%c cnt=%c%c orient=%d ui=%d density=%d touch=%d key=%d inp=%d nav=%d\n",
+                   group->getPath().string(), mParams.mcc, mParams.mnc,
+                   mParams.language[0] ? mParams.language[0] : '-',
+                   mParams.language[1] ? mParams.language[1] : '-',
+                   mParams.country[0] ? mParams.country[0] : '-',
+                   mParams.country[1] ? mParams.country[1] : '-',
+                   mParams.orientation, mParams.uiMode,
+                   mParams.density, mParams.touchscreen, mParams.keyboard,
+                   mParams.inputFlags, mParams.navigation));
+            mPath = "res";
+            mPath.appendPath(file->getGroupEntry().toDirName(mResType));
+            mPath.appendPath(leaf);
+            mBaseName = parseResourceName(leaf);
+            if (mBaseName == "") {
+                fprintf(stderr, "Error: malformed resource filename %s\n",
+                        file->getPrintableSource().string());
+                return UNKNOWN_ERROR;
+            }
+
+            NOISY(printf("file name=%s\n", mBaseName.string()));
+
+            return NO_ERROR;
+        }
+    }
+
+private:
+    String8 mResType;
+
+    const sp<ResourceTypeSet> mSet;
+    size_t mSetPos;
+
+    sp<AaptGroup> mGroup;
+    size_t mGroupPos;
+
+    sp<AaptFile> mFile;
+    String8 mBaseName;
+    String8 mLeafName;
+    String8 mPath;
+    ResTable_config mParams;
+};
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+bool isValidResourceType(const String8& type)
+{
+    return type == "anim" || type == "animator" || type == "interpolator"
+        || type == "transition" || type == "scene"
+        || type == "drawable" || type == "layout"
+        || type == "values" || type == "xml" || type == "raw"
+        || type == "color" || type == "menu" || type == "mipmap";
+}
+
+static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true)
+{
+    sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc"));
+    sp<AaptFile> file;
+    if (group != NULL) {
+        file = group->getFiles().valueFor(AaptGroupEntry());
+        if (file != NULL) {
+            return file;
+        }
+    }
+
+    if (!makeIfNecessary) {
+        return NULL;
+    }
+    return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(),
+                            NULL, String8());
+}
+
+static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
+    const sp<AaptGroup>& grp)
+{
+    if (grp->getFiles().size() != 1) {
+        fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
+                grp->getFiles().valueAt(0)->getPrintableSource().string());
+    }
+
+    sp<AaptFile> file = grp->getFiles().valueAt(0);
+
+    ResXMLTree block;
+    status_t err = parseXMLResource(file, &block);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    //printXMLBlock(&block);
+
+    ResXMLTree::event_code_t code;
+    while ((code=block.next()) != ResXMLTree::START_TAG
+           && code != ResXMLTree::END_DOCUMENT
+           && code != ResXMLTree::BAD_DOCUMENT) {
+    }
+
+    size_t len;
+    if (code != ResXMLTree::START_TAG) {
+        fprintf(stderr, "%s:%d: No start tag found\n",
+                file->getPrintableSource().string(), block.getLineNumber());
+        return UNKNOWN_ERROR;
+    }
+    if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
+        fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
+                file->getPrintableSource().string(), block.getLineNumber(),
+                String8(block.getElementName(&len)).string());
+        return UNKNOWN_ERROR;
+    }
+
+    ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
+    if (nameIndex < 0) {
+        fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
+                file->getPrintableSource().string(), block.getLineNumber());
+        return UNKNOWN_ERROR;
+    }
+
+    assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
+
+    String16 uses_sdk16("uses-sdk");
+    while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+           && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::START_TAG) {
+            if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) {
+                ssize_t minSdkIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
+                                                             "minSdkVersion");
+                if (minSdkIndex >= 0) {
+                    const uint16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len);
+                    const char* minSdk8 = strdup(String8(minSdk16).string());
+                    bundle->setManifestMinSdkVersion(minSdk8);
+                }
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
+                                  ResourceTable* table,
+                                  const sp<ResourceTypeSet>& set,
+                                  const char* resType)
+{
+    String8 type8(resType);
+    String16 type16(resType);
+
+    bool hasErrors = false;
+
+    ResourceDirIterator it(set, String8(resType));
+    ssize_t res;
+    while ((res=it.next()) == NO_ERROR) {
+        if (bundle->getVerbose()) {
+            printf("    (new resource id %s from %s)\n",
+                   it.getBaseName().string(), it.getFile()->getPrintableSource().string());
+        }
+        String16 baseName(it.getBaseName());
+        const char16_t* str = baseName.string();
+        const char16_t* const end = str + baseName.size();
+        while (str < end) {
+            if (!((*str >= 'a' && *str <= 'z')
+                    || (*str >= '0' && *str <= '9')
+                    || *str == '_' || *str == '.')) {
+                fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
+                        it.getPath().string());
+                hasErrors = true;
+            }
+            str++;
+        }
+        String8 resPath = it.getPath();
+        resPath.convertToResPath();
+        table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
+                        type16,
+                        baseName,
+                        String16(resPath),
+                        NULL,
+                        &it.getParams());
+        assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
+    }
+
+    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+class PreProcessImageWorkUnit : public WorkQueue::WorkUnit {
+public:
+    PreProcessImageWorkUnit(const Bundle* bundle, const sp<AaptAssets>& assets,
+            const sp<AaptFile>& file, volatile bool* hasErrors) :
+            mBundle(bundle), mAssets(assets), mFile(file), mHasErrors(hasErrors) {
+    }
+
+    virtual bool run() {
+        status_t status = preProcessImage(mBundle, mAssets, mFile, NULL);
+        if (status) {
+            *mHasErrors = true;
+        }
+        return true; // continue even if there are errors
+    }
+
+private:
+    const Bundle* mBundle;
+    sp<AaptAssets> mAssets;
+    sp<AaptFile> mFile;
+    volatile bool* mHasErrors;
+};
+
+static status_t preProcessImages(const Bundle* bundle, const sp<AaptAssets>& assets,
+                          const sp<ResourceTypeSet>& set, const char* type)
+{
+    volatile bool hasErrors = false;
+    ssize_t res = NO_ERROR;
+    if (bundle->getUseCrunchCache() == false) {
+        WorkQueue wq(MAX_THREADS, false);
+        ResourceDirIterator it(set, String8(type));
+        while ((res=it.next()) == NO_ERROR) {
+            PreProcessImageWorkUnit* w = new PreProcessImageWorkUnit(
+                    bundle, assets, it.getFile(), &hasErrors);
+            status_t status = wq.schedule(w);
+            if (status) {
+                fprintf(stderr, "preProcessImages failed: schedule() returned %d\n", status);
+                hasErrors = true;
+                delete w;
+                break;
+            }
+        }
+        status_t status = wq.finish();
+        if (status) {
+            fprintf(stderr, "preProcessImages failed: finish() returned %d\n", status);
+            hasErrors = true;
+        }
+    }
+    return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t postProcessImages(const sp<AaptAssets>& assets,
+                           ResourceTable* table,
+                           const sp<ResourceTypeSet>& set)
+{
+    ResourceDirIterator it(set, String8("drawable"));
+    bool hasErrors = false;
+    ssize_t res;
+    while ((res=it.next()) == NO_ERROR) {
+        res = postProcessImage(assets, table, it.getFile());
+        if (res < NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static void collect_files(const sp<AaptDir>& dir,
+        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
+{
+    const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
+    int N = groups.size();
+    for (int i=0; i<N; i++) {
+        String8 leafName = groups.keyAt(i);
+        const sp<AaptGroup>& group = groups.valueAt(i);
+
+        const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
+                = group->getFiles();
+
+        if (files.size() == 0) {
+            continue;
+        }
+
+        String8 resType = files.valueAt(0)->getResourceType();
+
+        ssize_t index = resources->indexOfKey(resType);
+
+        if (index < 0) {
+            sp<ResourceTypeSet> set = new ResourceTypeSet();
+            NOISY(printf("Creating new resource type set for leaf %s with group %s (%p)\n",
+                    leafName.string(), group->getPath().string(), group.get()));
+            set->add(leafName, group);
+            resources->add(resType, set);
+        } else {
+            sp<ResourceTypeSet> set = resources->valueAt(index);
+            index = set->indexOfKey(leafName);
+            if (index < 0) {
+                NOISY(printf("Adding to resource type set for leaf %s group %s (%p)\n",
+                        leafName.string(), group->getPath().string(), group.get()));
+                set->add(leafName, group);
+            } else {
+                sp<AaptGroup> existingGroup = set->valueAt(index);
+                NOISY(printf("Extending to resource type set for leaf %s group %s (%p)\n",
+                        leafName.string(), group->getPath().string(), group.get()));
+                for (size_t j=0; j<files.size(); j++) {
+                    NOISY(printf("Adding file %s in group %s resType %s\n",
+                        files.valueAt(j)->getSourceFile().string(),
+                        files.keyAt(j).toDirName(String8()).string(),
+                        resType.string()));
+                    status_t err = existingGroup->addFile(files.valueAt(j));
+                }
+            }
+        }
+    }
+}
+
+static void collect_files(const sp<AaptAssets>& ass,
+        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
+{
+    const Vector<sp<AaptDir> >& dirs = ass->resDirs();
+    int N = dirs.size();
+
+    for (int i=0; i<N; i++) {
+        sp<AaptDir> d = dirs.itemAt(i);
+        NOISY(printf("Collecting dir #%d %p: %s, leaf %s\n", i, d.get(), d->getPath().string(),
+                d->getLeaf().string()));
+        collect_files(d, resources);
+
+        // don't try to include the res dir
+        NOISY(printf("Removing dir leaf %s\n", d->getLeaf().string()));
+        ass->removeDir(d->getLeaf());
+    }
+}
+
+enum {
+    ATTR_OKAY = -1,
+    ATTR_NOT_FOUND = -2,
+    ATTR_LEADING_SPACES = -3,
+    ATTR_TRAILING_SPACES = -4
+};
+static int validateAttr(const String8& path, const ResTable& table,
+        const ResXMLParser& parser,
+        const char* ns, const char* attr, const char* validChars, bool required)
+{
+    size_t len;
+
+    ssize_t index = parser.indexOfAttribute(ns, attr);
+    const uint16_t* str;
+    Res_value value;
+    if (index >= 0 && parser.getAttributeValue(index, &value) >= 0) {
+        const ResStringPool* pool = &parser.getStrings();
+        if (value.dataType == Res_value::TYPE_REFERENCE) {
+            uint32_t specFlags = 0;
+            int strIdx;
+            if ((strIdx=table.resolveReference(&value, 0x10000000, NULL, &specFlags)) < 0) {
+                fprintf(stderr, "%s:%d: Tag <%s> attribute %s references unknown resid 0x%08x.\n",
+                        path.string(), parser.getLineNumber(),
+                        String8(parser.getElementName(&len)).string(), attr,
+                        value.data);
+                return ATTR_NOT_FOUND;
+            }
+            
+            pool = table.getTableStringBlock(strIdx);
+            #if 0
+            if (pool != NULL) {
+                str = pool->stringAt(value.data, &len);
+            }
+            printf("***** RES ATTR: %s specFlags=0x%x strIdx=%d: %s\n", attr,
+                    specFlags, strIdx, str != NULL ? String8(str).string() : "???");
+            #endif
+            if ((specFlags&~ResTable_typeSpec::SPEC_PUBLIC) != 0 && false) {
+                fprintf(stderr, "%s:%d: Tag <%s> attribute %s varies by configurations 0x%x.\n",
+                        path.string(), parser.getLineNumber(),
+                        String8(parser.getElementName(&len)).string(), attr,
+                        specFlags);
+                return ATTR_NOT_FOUND;
+            }
+        }
+        if (value.dataType == Res_value::TYPE_STRING) {
+            if (pool == NULL) {
+                fprintf(stderr, "%s:%d: Tag <%s> attribute %s has no string block.\n",
+                        path.string(), parser.getLineNumber(),
+                        String8(parser.getElementName(&len)).string(), attr);
+                return ATTR_NOT_FOUND;
+            }
+            if ((str=pool->stringAt(value.data, &len)) == NULL) {
+                fprintf(stderr, "%s:%d: Tag <%s> attribute %s has corrupt string value.\n",
+                        path.string(), parser.getLineNumber(),
+                        String8(parser.getElementName(&len)).string(), attr);
+                return ATTR_NOT_FOUND;
+            }
+        } else {
+            fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid type %d.\n",
+                    path.string(), parser.getLineNumber(),
+                    String8(parser.getElementName(&len)).string(), attr,
+                    value.dataType);
+            return ATTR_NOT_FOUND;
+        }
+        if (validChars) {
+            for (size_t i=0; i<len; i++) {
+                uint16_t c = str[i];
+                const char* p = validChars;
+                bool okay = false;
+                while (*p) {
+                    if (c == *p) {
+                        okay = true;
+                        break;
+                    }
+                    p++;
+                }
+                if (!okay) {
+                    fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid character '%c'.\n",
+                            path.string(), parser.getLineNumber(),
+                            String8(parser.getElementName(&len)).string(), attr, (char)str[i]);
+                    return (int)i;
+                }
+            }
+        }
+        if (*str == ' ') {
+            fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not start with a space.\n",
+                    path.string(), parser.getLineNumber(),
+                    String8(parser.getElementName(&len)).string(), attr);
+            return ATTR_LEADING_SPACES;
+        }
+        if (str[len-1] == ' ') {
+            fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not end with a space.\n",
+                    path.string(), parser.getLineNumber(),
+                    String8(parser.getElementName(&len)).string(), attr);
+            return ATTR_TRAILING_SPACES;
+        }
+        return ATTR_OKAY;
+    }
+    if (required) {
+        fprintf(stderr, "%s:%d: Tag <%s> missing required attribute %s.\n",
+                path.string(), parser.getLineNumber(),
+                String8(parser.getElementName(&len)).string(), attr);
+        return ATTR_NOT_FOUND;
+    }
+    return ATTR_OKAY;
+}
+
+static void checkForIds(const String8& path, ResXMLParser& parser)
+{
+    ResXMLTree::event_code_t code;
+    while ((code=parser.next()) != ResXMLTree::END_DOCUMENT
+           && code > ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::START_TAG) {
+            ssize_t index = parser.indexOfAttribute(NULL, "id");
+            if (index >= 0) {
+                fprintf(stderr, "%s:%d: warning: found plain 'id' attribute; did you mean the new 'android:id' name?\n",
+                        path.string(), parser.getLineNumber());
+            }
+        }
+    }
+}
+
+static bool applyFileOverlay(Bundle *bundle,
+                             const sp<AaptAssets>& assets,
+                             sp<ResourceTypeSet> *baseSet,
+                             const char *resType)
+{
+    if (bundle->getVerbose()) {
+        printf("applyFileOverlay for %s\n", resType);
+    }
+
+    // Replace any base level files in this category with any found from the overlay
+    // Also add any found only in the overlay.
+    sp<AaptAssets> overlay = assets->getOverlay();
+    String8 resTypeString(resType);
+
+    // work through the linked list of overlays
+    while (overlay.get()) {
+        KeyedVector<String8, sp<ResourceTypeSet> >* overlayRes = overlay->getResources();
+
+        // get the overlay resources of the requested type
+        ssize_t index = overlayRes->indexOfKey(resTypeString);
+        if (index >= 0) {
+            sp<ResourceTypeSet> overlaySet = overlayRes->valueAt(index);
+
+            // for each of the resources, check for a match in the previously built
+            // non-overlay "baseset".
+            size_t overlayCount = overlaySet->size();
+            for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) {
+                if (bundle->getVerbose()) {
+                    printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string());
+                }
+                size_t baseIndex = UNKNOWN_ERROR;
+                if (baseSet->get() != NULL) {
+                    baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex));
+                }
+                if (baseIndex < UNKNOWN_ERROR) {
+                    // look for same flavor.  For a given file (strings.xml, for example)
+                    // there may be a locale specific or other flavors - we want to match
+                    // the same flavor.
+                    sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
+                    sp<AaptGroup> baseGroup = (*baseSet)->valueAt(baseIndex);
+
+                    DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
+                            overlayGroup->getFiles();
+                    if (bundle->getVerbose()) {
+                        DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles =
+                                baseGroup->getFiles();
+                        for (size_t i=0; i < baseFiles.size(); i++) {
+                            printf("baseFile " ZD " has flavor %s\n", (ZD_TYPE) i,
+                                    baseFiles.keyAt(i).toString().string());
+                        }
+                        for (size_t i=0; i < overlayFiles.size(); i++) {
+                            printf("overlayFile " ZD " has flavor %s\n", (ZD_TYPE) i,
+                                    overlayFiles.keyAt(i).toString().string());
+                        }
+                    }
+
+                    size_t overlayGroupSize = overlayFiles.size();
+                    for (size_t overlayGroupIndex = 0;
+                            overlayGroupIndex<overlayGroupSize;
+                            overlayGroupIndex++) {
+                        size_t baseFileIndex =
+                                baseGroup->getFiles().indexOfKey(overlayFiles.
+                                keyAt(overlayGroupIndex));
+                        if (baseFileIndex < UNKNOWN_ERROR) {
+                            if (bundle->getVerbose()) {
+                                printf("found a match (" ZD ") for overlay file %s, for flavor %s\n",
+                                        (ZD_TYPE) baseFileIndex,
+                                        overlayGroup->getLeaf().string(),
+                                        overlayFiles.keyAt(overlayGroupIndex).toString().string());
+                            }
+                            baseGroup->removeFile(baseFileIndex);
+                        } else {
+                            // didn't find a match fall through and add it..
+                            if (true || bundle->getVerbose()) {
+                                printf("nothing matches overlay file %s, for flavor %s\n",
+                                        overlayGroup->getLeaf().string(),
+                                        overlayFiles.keyAt(overlayGroupIndex).toString().string());
+                            }
+                        }
+                        baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex));
+                        assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
+                    }
+                } else {
+                    if (baseSet->get() == NULL) {
+                        *baseSet = new ResourceTypeSet();
+                        assets->getResources()->add(String8(resType), *baseSet);
+                    }
+                    // this group doesn't exist (a file that's only in the overlay)
+                    (*baseSet)->add(overlaySet->keyAt(overlayIndex),
+                            overlaySet->valueAt(overlayIndex));
+                    // make sure all flavors are defined in the resources.
+                    sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
+                    DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
+                            overlayGroup->getFiles();
+                    size_t overlayGroupSize = overlayFiles.size();
+                    for (size_t overlayGroupIndex = 0;
+                            overlayGroupIndex<overlayGroupSize;
+                            overlayGroupIndex++) {
+                        assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
+                    }
+                }
+            }
+            // this overlay didn't have resources for this type
+        }
+        // try next overlay
+        overlay = overlay->getOverlay();
+    }
+    return true;
+}
+
+/*
+ * Inserts an attribute in a given node, only if the attribute does not
+ * exist.
+ * If errorOnFailedInsert is true, and the attribute already exists, returns false.
+ * Returns true otherwise, even if the attribute already exists.
+ */
+bool addTagAttribute(const sp<XMLNode>& node, const char* ns8,
+        const char* attr8, const char* value, bool errorOnFailedInsert)
+{
+    if (value == NULL) {
+        return true;
+    }
+
+    const String16 ns(ns8);
+    const String16 attr(attr8);
+
+    if (node->getAttribute(ns, attr) != NULL) {
+        if (errorOnFailedInsert) {
+            fprintf(stderr, "Error: AndroidManifest.xml already defines %s (in %s);"
+                            " cannot insert new value %s.\n",
+                    String8(attr).string(), String8(ns).string(), value);
+            return false;
+        }
+
+        fprintf(stderr, "Warning: AndroidManifest.xml already defines %s (in %s);"
+                        " using existing value in manifest.\n",
+                String8(attr).string(), String8(ns).string());
+
+        // don't stop the build.
+        return true;
+    }
+    
+    node->addAttribute(ns, attr, String16(value));
+    return true;
+}
+
+static void fullyQualifyClassName(const String8& package, sp<XMLNode> node,
+        const String16& attrName) {
+    XMLNode::attribute_entry* attr = node->editAttribute(
+            String16("http://schemas.android.com/apk/res/android"), attrName);
+    if (attr != NULL) {
+        String8 name(attr->string);
+
+        // asdf     --> package.asdf
+        // .asdf  .a.b  --> package.asdf package.a.b
+        // asdf.adsf --> asdf.asdf
+        String8 className;
+        const char* p = name.string();
+        const char* q = strchr(p, '.');
+        if (p == q) {
+            className += package;
+            className += name;
+        } else if (q == NULL) {
+            className += package;
+            className += ".";
+            className += name;
+        } else {
+            className += name;
+        }
+        NOISY(printf("Qualifying class '%s' to '%s'", name.string(), className.string()));
+        attr->string.setTo(String16(className));
+    }
+}
+
+status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
+{
+    root = root->searchElement(String16(), String16("manifest"));
+    if (root == NULL) {
+        fprintf(stderr, "No <manifest> tag.\n");
+        return UNKNOWN_ERROR;
+    }
+
+    bool errorOnFailedInsert = bundle->getErrorOnFailedInsert();
+
+    if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionCode",
+            bundle->getVersionCode(), errorOnFailedInsert)) {
+        return UNKNOWN_ERROR;
+    }
+    if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName",
+            bundle->getVersionName(), errorOnFailedInsert)) {
+        return UNKNOWN_ERROR;
+    }
+    
+    if (bundle->getMinSdkVersion() != NULL
+            || bundle->getTargetSdkVersion() != NULL
+            || bundle->getMaxSdkVersion() != NULL) {
+        sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk"));
+        if (vers == NULL) {
+            vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk"));
+            root->insertChildAt(vers, 0);
+        }
+        
+        if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "minSdkVersion",
+                bundle->getMinSdkVersion(), errorOnFailedInsert)) {
+            return UNKNOWN_ERROR;
+        }
+        if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "targetSdkVersion",
+                bundle->getTargetSdkVersion(), errorOnFailedInsert)) {
+            return UNKNOWN_ERROR;
+        }
+        if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "maxSdkVersion",
+                bundle->getMaxSdkVersion(), errorOnFailedInsert)) {
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    if (bundle->getDebugMode()) {
+        sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
+        if (application != NULL) {
+            if (!addTagAttribute(application, RESOURCES_ANDROID_NAMESPACE, "debuggable", "true",
+                    errorOnFailedInsert)) {
+                return UNKNOWN_ERROR;
+            }
+        }
+    }
+
+    // Deal with manifest package name overrides
+    const char* manifestPackageNameOverride = bundle->getManifestPackageNameOverride();
+    if (manifestPackageNameOverride != NULL) {
+        // Update the actual package name
+        XMLNode::attribute_entry* attr = root->editAttribute(String16(), String16("package"));
+        if (attr == NULL) {
+            fprintf(stderr, "package name is required with --rename-manifest-package.\n");
+            return UNKNOWN_ERROR;
+        }
+        String8 origPackage(attr->string);
+        attr->string.setTo(String16(manifestPackageNameOverride));
+        NOISY(printf("Overriding package '%s' to be '%s'\n", origPackage.string(), manifestPackageNameOverride));
+
+        // Make class names fully qualified
+        sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
+        if (application != NULL) {
+            fullyQualifyClassName(origPackage, application, String16("name"));
+            fullyQualifyClassName(origPackage, application, String16("backupAgent"));
+
+            Vector<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(application->getChildren());
+            for (size_t i = 0; i < children.size(); i++) {
+                sp<XMLNode> child = children.editItemAt(i);
+                String8 tag(child->getElementName());
+                if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
+                    fullyQualifyClassName(origPackage, child, String16("name"));
+                } else if (tag == "activity-alias") {
+                    fullyQualifyClassName(origPackage, child, String16("name"));
+                    fullyQualifyClassName(origPackage, child, String16("targetActivity"));
+                }
+            }
+        }
+    }
+
+    // Deal with manifest package name overrides
+    const char* instrumentationPackageNameOverride = bundle->getInstrumentationPackageNameOverride();
+    if (instrumentationPackageNameOverride != NULL) {
+        // Fix up instrumentation targets.
+        Vector<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(root->getChildren());
+        for (size_t i = 0; i < children.size(); i++) {
+            sp<XMLNode> child = children.editItemAt(i);
+            String8 tag(child->getElementName());
+            if (tag == "instrumentation") {
+                XMLNode::attribute_entry* attr = child->editAttribute(
+                        String16("http://schemas.android.com/apk/res/android"), String16("targetPackage"));
+                if (attr != NULL) {
+                    attr->string.setTo(String16(instrumentationPackageNameOverride));
+                }
+            }
+        }
+    }
+    
+    return NO_ERROR;
+}
+
+#define ASSIGN_IT(n) \
+        do { \
+            ssize_t index = resources->indexOfKey(String8(#n)); \
+            if (index >= 0) { \
+                n ## s = resources->valueAt(index); \
+            } \
+        } while (0)
+
+status_t updatePreProcessedCache(Bundle* bundle)
+{
+    #if BENCHMARK
+    fprintf(stdout, "BENCHMARK: Starting PNG PreProcessing \n");
+    long startPNGTime = clock();
+    #endif /* BENCHMARK */
+
+    String8 source(bundle->getResourceSourceDirs()[0]);
+    String8 dest(bundle->getCrunchedOutputDir());
+
+    FileFinder* ff = new SystemFileFinder();
+    CrunchCache cc(source,dest,ff);
+
+    CacheUpdater* cu = new SystemCacheUpdater(bundle);
+    size_t numFiles = cc.crunch(cu);
+
+    if (bundle->getVerbose())
+        fprintf(stdout, "Crunched %d PNG files to update cache\n", (int)numFiles);
+
+    delete ff;
+    delete cu;
+
+    #if BENCHMARK
+    fprintf(stdout, "BENCHMARK: End PNG PreProcessing. Time Elapsed: %f ms \n"
+            ,(clock() - startPNGTime)/1000.0);
+    #endif /* BENCHMARK */
+    return 0;
+}
+
+status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+    // First, look for a package file to parse.  This is required to
+    // be able to generate the resource information.
+    sp<AaptGroup> androidManifestFile =
+            assets->getFiles().valueFor(String8("AndroidManifest.xml"));
+    if (androidManifestFile == NULL) {
+        fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
+        return UNKNOWN_ERROR;
+    }
+
+    status_t err = parsePackage(bundle, assets, androidManifestFile);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    NOISY(printf("Creating resources for package %s\n",
+                 assets->getPackage().string()));
+
+    ResourceTable table(bundle, String16(assets->getPackage()));
+    err = table.addIncludedResources(bundle, assets);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    NOISY(printf("Found %d included resource packages\n", (int)table.size()));
+
+    // Standard flags for compiled XML and optional UTF-8 encoding
+    int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;
+
+    /* Only enable UTF-8 if the caller of aapt didn't specifically
+     * request UTF-16 encoding and the parameters of this package
+     * allow UTF-8 to be used.
+     */
+    if (!bundle->getUTF16StringsOption()) {
+        xmlFlags |= XML_COMPILE_UTF8;
+    }
+
+    // --------------------------------------------------------------
+    // First, gather all resource information.
+    // --------------------------------------------------------------
+
+    // resType -> leafName -> group
+    KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
+            new KeyedVector<String8, sp<ResourceTypeSet> >;
+    collect_files(assets, resources);
+
+    sp<ResourceTypeSet> drawables;
+    sp<ResourceTypeSet> layouts;
+    sp<ResourceTypeSet> anims;
+    sp<ResourceTypeSet> animators;
+    sp<ResourceTypeSet> interpolators;
+    sp<ResourceTypeSet> transitions;
+    sp<ResourceTypeSet> scenes;
+    sp<ResourceTypeSet> xmls;
+    sp<ResourceTypeSet> raws;
+    sp<ResourceTypeSet> colors;
+    sp<ResourceTypeSet> menus;
+    sp<ResourceTypeSet> mipmaps;
+
+    ASSIGN_IT(drawable);
+    ASSIGN_IT(layout);
+    ASSIGN_IT(anim);
+    ASSIGN_IT(animator);
+    ASSIGN_IT(interpolator);
+    ASSIGN_IT(transition);
+    ASSIGN_IT(scene);
+    ASSIGN_IT(xml);
+    ASSIGN_IT(raw);
+    ASSIGN_IT(color);
+    ASSIGN_IT(menu);
+    ASSIGN_IT(mipmap);
+
+    assets->setResources(resources);
+    // now go through any resource overlays and collect their files
+    sp<AaptAssets> current = assets->getOverlay();
+    while(current.get()) {
+        KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
+                new KeyedVector<String8, sp<ResourceTypeSet> >;
+        current->setResources(resources);
+        collect_files(current, resources);
+        current = current->getOverlay();
+    }
+    // apply the overlay files to the base set
+    if (!applyFileOverlay(bundle, assets, &drawables, "drawable") ||
+            !applyFileOverlay(bundle, assets, &layouts, "layout") ||
+            !applyFileOverlay(bundle, assets, &anims, "anim") ||
+            !applyFileOverlay(bundle, assets, &animators, "animator") ||
+            !applyFileOverlay(bundle, assets, &interpolators, "interpolator") ||
+            !applyFileOverlay(bundle, assets, &transitions, "transition") ||
+            !applyFileOverlay(bundle, assets, &scenes, "scene") ||
+            !applyFileOverlay(bundle, assets, &xmls, "xml") ||
+            !applyFileOverlay(bundle, assets, &raws, "raw") ||
+            !applyFileOverlay(bundle, assets, &colors, "color") ||
+            !applyFileOverlay(bundle, assets, &menus, "menu") ||
+            !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
+        return UNKNOWN_ERROR;
+    }
+
+    bool hasErrors = false;
+
+    if (drawables != NULL) {
+        if (bundle->getOutputAPKFile() != NULL) {
+            err = preProcessImages(bundle, assets, drawables, "drawable");
+        }
+        if (err == NO_ERROR) {
+            err = makeFileResources(bundle, assets, &table, drawables, "drawable");
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        } else {
+            hasErrors = true;
+        }
+    }
+
+    if (mipmaps != NULL) {
+        if (bundle->getOutputAPKFile() != NULL) {
+            err = preProcessImages(bundle, assets, mipmaps, "mipmap");
+        }
+        if (err == NO_ERROR) {
+            err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap");
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        } else {
+            hasErrors = true;
+        }
+    }
+
+    if (layouts != NULL) {
+        err = makeFileResources(bundle, assets, &table, layouts, "layout");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (anims != NULL) {
+        err = makeFileResources(bundle, assets, &table, anims, "anim");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (animators != NULL) {
+        err = makeFileResources(bundle, assets, &table, animators, "animator");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (transitions != NULL) {
+        err = makeFileResources(bundle, assets, &table, transitions, "transition");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (scenes != NULL) {
+        err = makeFileResources(bundle, assets, &table, scenes, "scene");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (interpolators != NULL) {
+        err = makeFileResources(bundle, assets, &table, interpolators, "interpolator");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (xmls != NULL) {
+        err = makeFileResources(bundle, assets, &table, xmls, "xml");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (raws != NULL) {
+        err = makeFileResources(bundle, assets, &table, raws, "raw");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    // compile resources
+    current = assets;
+    while(current.get()) {
+        KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
+                current->getResources();
+
+        ssize_t index = resources->indexOfKey(String8("values"));
+        if (index >= 0) {
+            ResourceDirIterator it(resources->valueAt(index), String8("values"));
+            ssize_t res;
+            while ((res=it.next()) == NO_ERROR) {
+                sp<AaptFile> file = it.getFile();
+                res = compileResourceFile(bundle, assets, file, it.getParams(), 
+                                          (current!=assets), &table);
+                if (res != NO_ERROR) {
+                    hasErrors = true;
+                }
+            }
+        }
+        current = current->getOverlay();
+    }
+
+    if (colors != NULL) {
+        err = makeFileResources(bundle, assets, &table, colors, "color");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (menus != NULL) {
+        err = makeFileResources(bundle, assets, &table, menus, "menu");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    // --------------------------------------------------------------------
+    // Assignment of resource IDs and initial generation of resource table.
+    // --------------------------------------------------------------------
+
+    if (table.hasResources()) {
+        sp<AaptFile> resFile(getResourceFile(assets));
+        if (resFile == NULL) {
+            fprintf(stderr, "Error: unable to generate entry for resource data\n");
+            return UNKNOWN_ERROR;
+        }
+
+        err = table.assignResourceIds();
+        if (err < NO_ERROR) {
+            return err;
+        }
+    }
+
+    // --------------------------------------------------------------
+    // Finally, we can now we can compile XML files, which may reference
+    // resources.
+    // --------------------------------------------------------------
+
+    if (layouts != NULL) {
+        ResourceDirIterator it(layouts, String8("layout"));
+        while ((err=it.next()) == NO_ERROR) {
+            String8 src = it.getFile()->getPrintableSource();
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err == NO_ERROR) {
+                ResXMLTree block;
+                block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
+                checkForIds(src, block);
+            } else {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (anims != NULL) {
+        ResourceDirIterator it(anims, String8("anim"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (animators != NULL) {
+        ResourceDirIterator it(animators, String8("animator"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (interpolators != NULL) {
+        ResourceDirIterator it(interpolators, String8("interpolator"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (transitions != NULL) {
+        ResourceDirIterator it(transitions, String8("transition"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (scenes != NULL) {
+        ResourceDirIterator it(scenes, String8("scene"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (xmls != NULL) {
+        ResourceDirIterator it(xmls, String8("xml"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (drawables != NULL) {
+        err = postProcessImages(assets, &table, drawables);
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    if (colors != NULL) {
+        ResourceDirIterator it(colors, String8("color"));
+        while ((err=it.next()) == NO_ERROR) {
+          err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (menus != NULL) {
+        ResourceDirIterator it(menus, String8("menu"));
+        while ((err=it.next()) == NO_ERROR) {
+            String8 src = it.getFile()->getPrintableSource();
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+            ResXMLTree block;
+            block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
+            checkForIds(src, block);
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
+    if (table.validateLocalizations()) {
+        hasErrors = true;
+    }
+    
+    if (hasErrors) {
+        return UNKNOWN_ERROR;
+    }
+
+    const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
+    String8 manifestPath(manifestFile->getPrintableSource());
+
+    // Generate final compiled manifest file.
+    manifestFile->clearData();
+    sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
+    if (manifestTree == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    err = massageManifest(bundle, manifestTree);
+    if (err < NO_ERROR) {
+        return err;
+    }
+    err = compileXmlFile(assets, manifestTree, manifestFile, &table);
+    if (err < NO_ERROR) {
+        return err;
+    }
+
+    //block.restart();
+    //printXMLBlock(&block);
+
+    // --------------------------------------------------------------
+    // Generate the final resource table.
+    // Re-flatten because we may have added new resource IDs
+    // --------------------------------------------------------------
+
+    ResTable finalResTable;
+    sp<AaptFile> resFile;
+    
+    if (table.hasResources()) {
+        sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+        err = table.addSymbols(symbols);
+        if (err < NO_ERROR) {
+            return err;
+        }
+
+        resFile = getResourceFile(assets);
+        if (resFile == NULL) {
+            fprintf(stderr, "Error: unable to generate entry for resource data\n");
+            return UNKNOWN_ERROR;
+        }
+
+        err = table.flatten(bundle, resFile);
+        if (err < NO_ERROR) {
+            return err;
+        }
+
+        if (bundle->getPublicOutputFile()) {
+            FILE* fp = fopen(bundle->getPublicOutputFile(), "w+");
+            if (fp == NULL) {
+                fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n",
+                        (const char*)bundle->getPublicOutputFile(), strerror(errno));
+                return UNKNOWN_ERROR;
+            }
+            if (bundle->getVerbose()) {
+                printf("  Writing public definitions to %s.\n", bundle->getPublicOutputFile());
+            }
+            table.writePublicDefinitions(String16(assets->getPackage()), fp);
+            fclose(fp);
+        }
+        
+        // Read resources back in,
+        finalResTable.add(resFile->getData(), resFile->getSize(), NULL);
+        
+#if 0
+        NOISY(
+              printf("Generated resources:\n");
+              finalResTable.print();
+        )
+#endif
+    }
+    
+    // Perform a basic validation of the manifest file.  This time we
+    // parse it with the comments intact, so that we can use them to
+    // generate java docs...  so we are not going to write this one
+    // back out to the final manifest data.
+    sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(),
+            manifestFile->getGroupEntry(),
+            manifestFile->getResourceType());
+    err = compileXmlFile(assets, manifestFile,
+            outManifestFile, &table,
+            XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+            | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+    if (err < NO_ERROR) {
+        return err;
+    }
+    ResXMLTree block;
+    block.setTo(outManifestFile->getData(), outManifestFile->getSize(), true);
+    String16 manifest16("manifest");
+    String16 permission16("permission");
+    String16 permission_group16("permission-group");
+    String16 uses_permission16("uses-permission");
+    String16 instrumentation16("instrumentation");
+    String16 application16("application");
+    String16 provider16("provider");
+    String16 service16("service");
+    String16 receiver16("receiver");
+    String16 activity16("activity");
+    String16 action16("action");
+    String16 category16("category");
+    String16 data16("scheme");
+    const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
+    const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
+    const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$";
+    const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:";
+    const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;";
+    const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+";
+    const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
+    ResXMLTree::event_code_t code;
+    sp<AaptSymbols> permissionSymbols;
+    sp<AaptSymbols> permissionGroupSymbols;
+    while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+           && code > ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::START_TAG) {
+            size_t len;
+            if (block.getElementNamespace(&len) != NULL) {
+                continue;
+            }
+            if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block, NULL, "package",
+                                 packageIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+                                 "sharedUserId", packageIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0
+                    || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) {
+                const bool isGroup = strcmp16(block.getElementName(&len),
+                        permission_group16.string()) == 0;
+                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+                                 "name", isGroup ? packageIdentCharsWithTheStupid
+                                 : packageIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                SourcePos srcPos(manifestPath, block.getLineNumber());
+                sp<AaptSymbols> syms;
+                if (!isGroup) {
+                    syms = permissionSymbols;
+                    if (syms == NULL) {
+                        sp<AaptSymbols> symbols =
+                                assets->getSymbolsFor(String8("Manifest"));
+                        syms = permissionSymbols = symbols->addNestedSymbol(
+                                String8("permission"), srcPos);
+                    }
+                } else {
+                    syms = permissionGroupSymbols;
+                    if (syms == NULL) {
+                        sp<AaptSymbols> symbols =
+                                assets->getSymbolsFor(String8("Manifest"));
+                        syms = permissionGroupSymbols = symbols->addNestedSymbol(
+                                String8("permission_group"), srcPos);
+                    }
+                }
+                size_t len;
+                ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name");
+                const uint16_t* id = block.getAttributeStringValue(index, &len);
+                if (id == NULL) {
+                    fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n", 
+                            manifestPath.string(), block.getLineNumber(),
+                            String8(block.getElementName(&len)).string());
+                    hasErrors = true;
+                    break;
+                }
+                String8 idStr(id);
+                char* p = idStr.lockBuffer(idStr.size());
+                char* e = p + idStr.size();
+                bool begins_with_digit = true;  // init to true so an empty string fails
+                while (e > p) {
+                    e--;
+                    if (*e >= '0' && *e <= '9') {
+                      begins_with_digit = true;
+                      continue;
+                    }
+                    if ((*e >= 'a' && *e <= 'z') ||
+                        (*e >= 'A' && *e <= 'Z') ||
+                        (*e == '_')) {
+                      begins_with_digit = false;
+                      continue;
+                    }
+                    if (isGroup && (*e == '-')) {
+                        *e = '_';
+                        begins_with_digit = false;
+                        continue;
+                    }
+                    e++;
+                    break;
+                }
+                idStr.unlockBuffer();
+                // verify that we stopped because we hit a period or
+                // the beginning of the string, and that the
+                // identifier didn't begin with a digit.
+                if (begins_with_digit || (e != p && *(e-1) != '.')) {
+                  fprintf(stderr,
+                          "%s:%d: Permission name <%s> is not a valid Java symbol\n",
+                          manifestPath.string(), block.getLineNumber(), idStr.string());
+                  hasErrors = true;
+                }
+                syms->addStringSymbol(String8(e), idStr, srcPos);
+                const uint16_t* cmt = block.getComment(&len);
+                if (cmt != NULL && *cmt != 0) {
+                    //printf("Comment of %s: %s\n", String8(e).string(),
+                    //        String8(cmt).string());
+                    syms->appendComment(String8(e), String16(cmt), srcPos);
+                } else {
+                    //printf("No comment for %s\n", String8(e).string());
+                }
+                syms->makeSymbolPublic(String8(e), srcPos);
+            } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+                                 "name", packageIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+                                 "name", classIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "targetPackage",
+                                 packageIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+                                 "name", classIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "permission",
+                                 packageIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "process",
+                                 processIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
+                                 processIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+                                 "name", classIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "authorities",
+                                 authoritiesIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "permission",
+                                 packageIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "process",
+                                 processIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), service16.string()) == 0
+                       || strcmp16(block.getElementName(&len), receiver16.string()) == 0
+                       || strcmp16(block.getElementName(&len), activity16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+                                 "name", classIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "permission",
+                                 packageIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "process",
+                                 processIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
+                                 processIdentChars, false) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), action16.string()) == 0
+                       || strcmp16(block.getElementName(&len), category16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "name",
+                                 packageIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) {
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "mimeType",
+                                 typeIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+                if (validateAttr(manifestPath, finalResTable, block,
+                                 RESOURCES_ANDROID_NAMESPACE, "scheme",
+                                 schemeIdentChars, true) != ATTR_OKAY) {
+                    hasErrors = true;
+                }
+            }
+        }
+    }
+
+    if (resFile != NULL) {
+        // These resources are now considered to be a part of the included
+        // resources, for others to reference.
+        err = assets->addIncludedResources(resFile);
+        if (err < NO_ERROR) {
+            fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
+            return err;
+        }
+    }
+    
+    return err;
+}
+
+static const char* getIndentSpace(int indent)
+{
+static const char whitespace[] =
+"                                                                                       ";
+
+    return whitespace + sizeof(whitespace) - 1 - indent*4;
+}
+
+static String8 flattenSymbol(const String8& symbol) {
+    String8 result(symbol);
+    ssize_t first;
+    if ((first = symbol.find(":", 0)) >= 0
+            || (first = symbol.find(".", 0)) >= 0) {
+        size_t size = symbol.size();
+        char* buf = result.lockBuffer(size);
+        for (size_t i = first; i < size; i++) {
+            if (buf[i] == ':' || buf[i] == '.') {
+                buf[i] = '_';
+            }
+        }
+        result.unlockBuffer(size);
+    }
+    return result;
+}
+
+static String8 getSymbolPackage(const String8& symbol, const sp<AaptAssets>& assets, bool pub) {
+    ssize_t colon = symbol.find(":", 0);
+    if (colon >= 0) {
+        return String8(symbol.string(), colon);
+    }
+    return pub ? assets->getPackage() : assets->getSymbolsPrivatePackage();
+}
+
+static String8 getSymbolName(const String8& symbol) {
+    ssize_t colon = symbol.find(":", 0);
+    if (colon >= 0) {
+        return String8(symbol.string() + colon + 1);
+    }
+    return symbol;
+}
+
+static String16 getAttributeComment(const sp<AaptAssets>& assets,
+                                    const String8& name,
+                                    String16* outTypeComment = NULL)
+{
+    sp<AaptSymbols> asym = assets->getSymbolsFor(String8("R"));
+    if (asym != NULL) {
+        //printf("Got R symbols!\n");
+        asym = asym->getNestedSymbols().valueFor(String8("attr"));
+        if (asym != NULL) {
+            //printf("Got attrs symbols! comment %s=%s\n",
+            //     name.string(), String8(asym->getComment(name)).string());
+            if (outTypeComment != NULL) {
+                *outTypeComment = asym->getTypeComment(name);
+            }
+            return asym->getComment(name);
+        }
+    }
+    return String16();
+}
+
+static status_t writeLayoutClasses(
+    FILE* fp, const sp<AaptAssets>& assets,
+    const sp<AaptSymbols>& symbols, int indent, bool includePrivate)
+{
+    const char* indentStr = getIndentSpace(indent);
+    if (!includePrivate) {
+        fprintf(fp, "%s/** @doconly */\n", indentStr);
+    }
+    fprintf(fp, "%spublic static final class styleable {\n", indentStr);
+    indent++;
+
+    String16 attr16("attr");
+    String16 package16(assets->getPackage());
+
+    indentStr = getIndentSpace(indent);
+    bool hasErrors = false;
+
+    size_t i;
+    size_t N = symbols->getNestedSymbols().size();
+    for (i=0; i<N; i++) {
+        sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+        String8 realClassName(symbols->getNestedSymbols().keyAt(i));
+        String8 nclassName(flattenSymbol(realClassName));
+
+        SortedVector<uint32_t> idents;
+        Vector<uint32_t> origOrder;
+        Vector<bool> publicFlags;
+
+        size_t a;
+        size_t NA = nsymbols->getSymbols().size();
+        for (a=0; a<NA; a++) {
+            const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
+            int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
+                    ? sym.int32Val : 0;
+            bool isPublic = true;
+            if (code == 0) {
+                String16 name16(sym.name);
+                uint32_t typeSpecFlags;
+                code = assets->getIncludedResources().identifierForName(
+                    name16.string(), name16.size(),
+                    attr16.string(), attr16.size(),
+                    package16.string(), package16.size(), &typeSpecFlags);
+                if (code == 0) {
+                    fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
+                            nclassName.string(), sym.name.string());
+                    hasErrors = true;
+                }
+                isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+            }
+            idents.add(code);
+            origOrder.add(code);
+            publicFlags.add(isPublic);
+        }
+
+        NA = idents.size();
+
+        bool deprecated = false;
+        
+        String16 comment = symbols->getComment(realClassName);
+        fprintf(fp, "%s/** ", indentStr);
+        if (comment.size() > 0) {
+            String8 cmt(comment);
+            fprintf(fp, "%s\n", cmt.string());
+            if (strstr(cmt.string(), "@deprecated") != NULL) {
+                deprecated = true;
+            }
+        } else {
+            fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string());
+        }
+        bool hasTable = false;
+        for (a=0; a<NA; a++) {
+            ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+            if (pos >= 0) {
+                if (!hasTable) {
+                    hasTable = true;
+                    fprintf(fp,
+                            "%s   <p>Includes the following attributes:</p>\n"
+                            "%s   <table>\n"
+                            "%s   <colgroup align=\"left\" />\n"
+                            "%s   <colgroup align=\"left\" />\n"
+                            "%s   <tr><th>Attribute</th><th>Description</th></tr>\n",
+                            indentStr,
+                            indentStr,
+                            indentStr,
+                            indentStr,
+                            indentStr);
+                }
+                const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+                if (!publicFlags.itemAt(a) && !includePrivate) {
+                    continue;
+                }
+                String8 name8(sym.name);
+                String16 comment(sym.comment);
+                if (comment.size() <= 0) {
+                    comment = getAttributeComment(assets, name8);
+                }
+                if (comment.size() > 0) {
+                    const char16_t* p = comment.string();
+                    while (*p != 0 && *p != '.') {
+                        if (*p == '{') {
+                            while (*p != 0 && *p != '}') {
+                                p++;
+                            }
+                        } else {
+                            p++;
+                        }
+                    }
+                    if (*p == '.') {
+                        p++;
+                    }
+                    comment = String16(comment.string(), p-comment.string());
+                }
+                fprintf(fp, "%s   <tr><td><code>{@link #%s_%s %s:%s}</code></td><td>%s</td></tr>\n",
+                        indentStr, nclassName.string(),
+                        flattenSymbol(name8).string(),
+                        getSymbolPackage(name8, assets, true).string(),
+                        getSymbolName(name8).string(),
+                        String8(comment).string());
+            }
+        }
+        if (hasTable) {
+            fprintf(fp, "%s   </table>\n", indentStr);
+        }
+        for (a=0; a<NA; a++) {
+            ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+            if (pos >= 0) {
+                const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+                if (!publicFlags.itemAt(a) && !includePrivate) {
+                    continue;
+                }
+                fprintf(fp, "%s   @see #%s_%s\n",
+                        indentStr, nclassName.string(),
+                        flattenSymbol(sym.name).string());
+            }
+        }
+        fprintf(fp, "%s */\n", getIndentSpace(indent));
+
+        if (deprecated) {
+            fprintf(fp, "%s@Deprecated\n", indentStr);
+        }
+        
+        fprintf(fp,
+                "%spublic static final int[] %s = {\n"
+                "%s",
+                indentStr, nclassName.string(),
+                getIndentSpace(indent+1));
+
+        for (a=0; a<NA; a++) {
+            if (a != 0) {
+                if ((a&3) == 0) {
+                    fprintf(fp, ",\n%s", getIndentSpace(indent+1));
+                } else {
+                    fprintf(fp, ", ");
+                }
+            }
+            fprintf(fp, "0x%08x", idents[a]);
+        }
+
+        fprintf(fp, "\n%s};\n", indentStr);
+
+        for (a=0; a<NA; a++) {
+            ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+            if (pos >= 0) {
+                const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+                if (!publicFlags.itemAt(a) && !includePrivate) {
+                    continue;
+                }
+                String8 name8(sym.name);
+                String16 comment(sym.comment);
+                String16 typeComment;
+                if (comment.size() <= 0) {
+                    comment = getAttributeComment(assets, name8, &typeComment);
+                } else {
+                    getAttributeComment(assets, name8, &typeComment);
+                }
+
+                uint32_t typeSpecFlags = 0;
+                String16 name16(sym.name);
+                assets->getIncludedResources().identifierForName(
+                    name16.string(), name16.size(),
+                    attr16.string(), attr16.size(),
+                    package16.string(), package16.size(), &typeSpecFlags);
+                //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
+                //    String8(attr16).string(), String8(name16).string(), typeSpecFlags);
+                const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+                
+                bool deprecated = false;
+                
+                fprintf(fp, "%s/**\n", indentStr);
+                if (comment.size() > 0) {
+                    String8 cmt(comment);
+                    fprintf(fp, "%s  <p>\n%s  @attr description\n", indentStr, indentStr);
+                    fprintf(fp, "%s  %s\n", indentStr, cmt.string());
+                    if (strstr(cmt.string(), "@deprecated") != NULL) {
+                        deprecated = true;
+                    }
+                } else {
+                    fprintf(fp,
+                            "%s  <p>This symbol is the offset where the {@link %s.R.attr#%s}\n"
+                            "%s  attribute's value can be found in the {@link #%s} array.\n",
+                            indentStr,
+                            getSymbolPackage(name8, assets, pub).string(),
+                            getSymbolName(name8).string(),
+                            indentStr, nclassName.string());
+                }
+                if (typeComment.size() > 0) {
+                    String8 cmt(typeComment);
+                    fprintf(fp, "\n\n%s  %s\n", indentStr, cmt.string());
+                    if (strstr(cmt.string(), "@deprecated") != NULL) {
+                        deprecated = true;
+                    }
+                }
+                if (comment.size() > 0) {
+                    if (pub) {
+                        fprintf(fp,
+                                "%s  <p>This corresponds to the global attribute\n"
+                                "%s  resource symbol {@link %s.R.attr#%s}.\n",
+                                indentStr, indentStr,
+                                getSymbolPackage(name8, assets, true).string(),
+                                getSymbolName(name8).string());
+                    } else {
+                        fprintf(fp,
+                                "%s  <p>This is a private symbol.\n", indentStr);
+                    }
+                }
+                fprintf(fp, "%s  @attr name %s:%s\n", indentStr,
+                        getSymbolPackage(name8, assets, pub).string(),
+                        getSymbolName(name8).string());
+                fprintf(fp, "%s*/\n", indentStr);
+                if (deprecated) {
+                    fprintf(fp, "%s@Deprecated\n", indentStr);
+                }
+                fprintf(fp,
+                        "%spublic static final int %s_%s = %d;\n",
+                        indentStr, nclassName.string(),
+                        flattenSymbol(name8).string(), (int)pos);
+            }
+        }
+    }
+
+    indent--;
+    fprintf(fp, "%s};\n", getIndentSpace(indent));
+    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static status_t writeTextLayoutClasses(
+    FILE* fp, const sp<AaptAssets>& assets,
+    const sp<AaptSymbols>& symbols, bool includePrivate)
+{
+    String16 attr16("attr");
+    String16 package16(assets->getPackage());
+
+    bool hasErrors = false;
+
+    size_t i;
+    size_t N = symbols->getNestedSymbols().size();
+    for (i=0; i<N; i++) {
+        sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+        String8 realClassName(symbols->getNestedSymbols().keyAt(i));
+        String8 nclassName(flattenSymbol(realClassName));
+
+        SortedVector<uint32_t> idents;
+        Vector<uint32_t> origOrder;
+        Vector<bool> publicFlags;
+
+        size_t a;
+        size_t NA = nsymbols->getSymbols().size();
+        for (a=0; a<NA; a++) {
+            const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
+            int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
+                    ? sym.int32Val : 0;
+            bool isPublic = true;
+            if (code == 0) {
+                String16 name16(sym.name);
+                uint32_t typeSpecFlags;
+                code = assets->getIncludedResources().identifierForName(
+                    name16.string(), name16.size(),
+                    attr16.string(), attr16.size(),
+                    package16.string(), package16.size(), &typeSpecFlags);
+                if (code == 0) {
+                    fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
+                            nclassName.string(), sym.name.string());
+                    hasErrors = true;
+                }
+                isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+            }
+            idents.add(code);
+            origOrder.add(code);
+            publicFlags.add(isPublic);
+        }
+
+        NA = idents.size();
+
+        fprintf(fp, "int[] styleable %s {", nclassName.string());
+
+        for (a=0; a<NA; a++) {
+            if (a != 0) {
+                fprintf(fp, ",");
+            }
+            fprintf(fp, " 0x%08x", idents[a]);
+        }
+
+        fprintf(fp, " }\n");
+
+        for (a=0; a<NA; a++) {
+            ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+            if (pos >= 0) {
+                const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+                if (!publicFlags.itemAt(a) && !includePrivate) {
+                    continue;
+                }
+                String8 name8(sym.name);
+                String16 comment(sym.comment);
+                String16 typeComment;
+                if (comment.size() <= 0) {
+                    comment = getAttributeComment(assets, name8, &typeComment);
+                } else {
+                    getAttributeComment(assets, name8, &typeComment);
+                }
+
+                uint32_t typeSpecFlags = 0;
+                String16 name16(sym.name);
+                assets->getIncludedResources().identifierForName(
+                    name16.string(), name16.size(),
+                    attr16.string(), attr16.size(),
+                    package16.string(), package16.size(), &typeSpecFlags);
+                //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
+                //    String8(attr16).string(), String8(name16).string(), typeSpecFlags);
+                const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+
+                fprintf(fp,
+                        "int styleable %s_%s %d\n",
+                        nclassName.string(),
+                        flattenSymbol(name8).string(), (int)pos);
+            }
+        }
+    }
+
+    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static status_t writeSymbolClass(
+    FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
+    const sp<AaptSymbols>& symbols, const String8& className, int indent,
+    bool nonConstantId)
+{
+    fprintf(fp, "%spublic %sfinal class %s {\n",
+            getIndentSpace(indent),
+            indent != 0 ? "static " : "", className.string());
+    indent++;
+
+    size_t i;
+    status_t err = NO_ERROR;
+
+    const char * id_format = nonConstantId ?
+            "%spublic static int %s=0x%08x;\n" :
+            "%spublic static final int %s=0x%08x;\n";
+
+    size_t N = symbols->getSymbols().size();
+    for (i=0; i<N; i++) {
+        const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+        if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
+            continue;
+        }
+        if (!assets->isJavaSymbol(sym, includePrivate)) {
+            continue;
+        }
+        String8 name8(sym.name);
+        String16 comment(sym.comment);
+        bool haveComment = false;
+        bool deprecated = false;
+        if (comment.size() > 0) {
+            haveComment = true;
+            String8 cmt(comment);
+            fprintf(fp,
+                    "%s/** %s\n",
+                    getIndentSpace(indent), cmt.string());
+            if (strstr(cmt.string(), "@deprecated") != NULL) {
+                deprecated = true;
+            }
+        } else if (sym.isPublic && !includePrivate) {
+            sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
+                assets->getPackage().string(), className.string(),
+                String8(sym.name).string());
+        }
+        String16 typeComment(sym.typeComment);
+        if (typeComment.size() > 0) {
+            String8 cmt(typeComment);
+            if (!haveComment) {
+                haveComment = true;
+                fprintf(fp,
+                        "%s/** %s\n", getIndentSpace(indent), cmt.string());
+            } else {
+                fprintf(fp,
+                        "%s %s\n", getIndentSpace(indent), cmt.string());
+            }
+            if (strstr(cmt.string(), "@deprecated") != NULL) {
+                deprecated = true;
+            }
+        }
+        if (haveComment) {
+            fprintf(fp,"%s */\n", getIndentSpace(indent));
+        }
+        if (deprecated) {
+            fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
+        }
+        fprintf(fp, id_format,
+                getIndentSpace(indent),
+                flattenSymbol(name8).string(), (int)sym.int32Val);
+    }
+
+    for (i=0; i<N; i++) {
+        const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+        if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) {
+            continue;
+        }
+        if (!assets->isJavaSymbol(sym, includePrivate)) {
+            continue;
+        }
+        String8 name8(sym.name);
+        String16 comment(sym.comment);
+        bool deprecated = false;
+        if (comment.size() > 0) {
+            String8 cmt(comment);
+            fprintf(fp,
+                    "%s/** %s\n"
+                     "%s */\n",
+                    getIndentSpace(indent), cmt.string(),
+                    getIndentSpace(indent));
+            if (strstr(cmt.string(), "@deprecated") != NULL) {
+                deprecated = true;
+            }
+        } else if (sym.isPublic && !includePrivate) {
+            sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
+                assets->getPackage().string(), className.string(),
+                String8(sym.name).string());
+        }
+        if (deprecated) {
+            fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
+        }
+        fprintf(fp, "%spublic static final String %s=\"%s\";\n",
+                getIndentSpace(indent),
+                flattenSymbol(name8).string(), sym.stringVal.string());
+    }
+
+    sp<AaptSymbols> styleableSymbols;
+
+    N = symbols->getNestedSymbols().size();
+    for (i=0; i<N; i++) {
+        sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+        String8 nclassName(symbols->getNestedSymbols().keyAt(i));
+        if (nclassName == "styleable") {
+            styleableSymbols = nsymbols;
+        } else {
+            err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent, nonConstantId);
+        }
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    if (styleableSymbols != NULL) {
+        err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    indent--;
+    fprintf(fp, "%s}\n", getIndentSpace(indent));
+    return NO_ERROR;
+}
+
+static status_t writeTextSymbolClass(
+    FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
+    const sp<AaptSymbols>& symbols, const String8& className)
+{
+    size_t i;
+    status_t err = NO_ERROR;
+
+    size_t N = symbols->getSymbols().size();
+    for (i=0; i<N; i++) {
+        const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+        if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
+            continue;
+        }
+
+        if (!assets->isJavaSymbol(sym, includePrivate)) {
+            continue;
+        }
+
+        String8 name8(sym.name);
+        fprintf(fp, "int %s %s 0x%08x\n",
+                className.string(),
+                flattenSymbol(name8).string(), (int)sym.int32Val);
+    }
+
+    N = symbols->getNestedSymbols().size();
+    for (i=0; i<N; i++) {
+        sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+        String8 nclassName(symbols->getNestedSymbols().keyAt(i));
+        if (nclassName == "styleable") {
+            err = writeTextLayoutClasses(fp, assets, nsymbols, includePrivate);
+        } else {
+            err = writeTextSymbolClass(fp, assets, includePrivate, nsymbols, nclassName);
+        }
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
+    const String8& package, bool includePrivate)
+{
+    if (!bundle->getRClassDir()) {
+        return NO_ERROR;
+    }
+
+    const char* textSymbolsDest = bundle->getOutputTextSymbols();
+
+    String8 R("R");
+    const size_t N = assets->getSymbols().size();
+    for (size_t i=0; i<N; i++) {
+        sp<AaptSymbols> symbols = assets->getSymbols().valueAt(i);
+        String8 className(assets->getSymbols().keyAt(i));
+        String8 dest(bundle->getRClassDir());
+
+        if (bundle->getMakePackageDirs()) {
+            String8 pkg(package);
+            const char* last = pkg.string();
+            const char* s = last-1;
+            do {
+                s++;
+                if (s > last && (*s == '.' || *s == 0)) {
+                    String8 part(last, s-last);
+                    dest.appendPath(part);
+#ifdef HAVE_MS_C_RUNTIME
+                    _mkdir(dest.string());
+#else
+                    mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+                    last = s+1;
+                }
+            } while (*s);
+        }
+        dest.appendPath(className);
+        dest.append(".java");
+        FILE* fp = fopen(dest.string(), "w+");
+        if (fp == NULL) {
+            fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
+                    dest.string(), strerror(errno));
+            return UNKNOWN_ERROR;
+        }
+        if (bundle->getVerbose()) {
+            printf("  Writing symbols for class %s.\n", className.string());
+        }
+
+        fprintf(fp,
+            "/* AUTO-GENERATED FILE.  DO NOT MODIFY.\n"
+            " *\n"
+            " * This class was automatically generated by the\n"
+            " * aapt tool from the resource data it found.  It\n"
+            " * should not be modified by hand.\n"
+            " */\n"
+            "\n"
+            "package %s;\n\n", package.string());
+
+        status_t err = writeSymbolClass(fp, assets, includePrivate, symbols,
+                className, 0, bundle->getNonConstantId());
+        if (err != NO_ERROR) {
+            return err;
+        }
+        fclose(fp);
+
+        if (textSymbolsDest != NULL && R == className) {
+            String8 textDest(textSymbolsDest);
+            textDest.appendPath(className);
+            textDest.append(".txt");
+
+            FILE* fp = fopen(textDest.string(), "w+");
+            if (fp == NULL) {
+                fprintf(stderr, "ERROR: Unable to open text symbol file %s: %s\n",
+                        textDest.string(), strerror(errno));
+                return UNKNOWN_ERROR;
+            }
+            if (bundle->getVerbose()) {
+                printf("  Writing text symbols for class %s.\n", className.string());
+            }
+
+            status_t err = writeTextSymbolClass(fp, assets, includePrivate, symbols,
+                    className);
+            if (err != NO_ERROR) {
+                return err;
+            }
+            fclose(fp);
+        }
+
+        // If we were asked to generate a dependency file, we'll go ahead and add this R.java
+        // as a target in the dependency file right next to it.
+        if (bundle->getGenDependencies() && R == className) {
+            // Add this R.java to the dependency file
+            String8 dependencyFile(bundle->getRClassDir());
+            dependencyFile.appendPath("R.java.d");
+
+            FILE *fp = fopen(dependencyFile.string(), "a");
+            fprintf(fp,"%s \\\n", dest.string());
+            fclose(fp);
+        }
+    }
+
+    return NO_ERROR;
+}
+
+
+class ProguardKeepSet
+{
+public:
+    // { rule --> { file locations } }
+    KeyedVector<String8, SortedVector<String8> > rules;
+
+    void add(const String8& rule, const String8& where);
+};
+
+void ProguardKeepSet::add(const String8& rule, const String8& where)
+{
+    ssize_t index = rules.indexOfKey(rule);
+    if (index < 0) {
+        index = rules.add(rule, SortedVector<String8>());
+    }
+    rules.editValueAt(index).add(where);
+}
+
+void
+addProguardKeepRule(ProguardKeepSet* keep, const String8& inClassName,
+        const char* pkg, const String8& srcName, int line)
+{
+    String8 className(inClassName);
+    if (pkg != NULL) {
+        // asdf     --> package.asdf
+        // .asdf  .a.b  --> package.asdf package.a.b
+        // asdf.adsf --> asdf.asdf
+        const char* p = className.string();
+        const char* q = strchr(p, '.');
+        if (p == q) {
+            className = pkg;
+            className.append(inClassName);
+        } else if (q == NULL) {
+            className = pkg;
+            className.append(".");
+            className.append(inClassName);
+        }
+    }
+
+    String8 rule("-keep class ");
+    rule += className;
+    rule += " { <init>(...); }";
+
+    String8 location("view ");
+    location += srcName;
+    char lineno[20];
+    sprintf(lineno, ":%d", line);
+    location += lineno;
+
+    keep->add(rule, location);
+}
+
+void
+addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName,
+        const char* pkg, const String8& srcName, int line)
+{
+    String8 rule("-keepclassmembers class * { *** ");
+    rule += memberName;
+    rule += "(...); }";
+
+    String8 location("onClick ");
+    location += srcName;
+    char lineno[20];
+    sprintf(lineno, ":%d", line);
+    location += lineno;
+
+    keep->add(rule, location);
+}
+
+status_t
+writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
+{
+    status_t err;
+    ResXMLTree tree;
+    size_t len;
+    ResXMLTree::event_code_t code;
+    int depth = 0;
+    bool inApplication = false;
+    String8 error;
+    sp<AaptGroup> assGroup;
+    sp<AaptFile> assFile;
+    String8 pkg;
+
+    // First, look for a package file to parse.  This is required to
+    // be able to generate the resource information.
+    assGroup = assets->getFiles().valueFor(String8("AndroidManifest.xml"));
+    if (assGroup == NULL) {
+        fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
+        return -1;
+    }
+
+    if (assGroup->getFiles().size() != 1) {
+        fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
+                assGroup->getFiles().valueAt(0)->getPrintableSource().string());
+    }
+
+    assFile = assGroup->getFiles().valueAt(0);
+
+    err = parseXMLResource(assFile, &tree);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    tree.restart();
+
+    while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::END_TAG) {
+            if (/* name == "Application" && */ depth == 2) {
+                inApplication = false;
+            }
+            depth--;
+            continue;
+        }
+        if (code != ResXMLTree::START_TAG) {
+            continue;
+        }
+        depth++;
+        String8 tag(tree.getElementName(&len));
+        // printf("Depth %d tag %s\n", depth, tag.string());
+        bool keepTag = false;
+        if (depth == 1) {
+            if (tag != "manifest") {
+                fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+                return -1;
+            }
+            pkg = getAttribute(tree, NULL, "package", NULL);
+        } else if (depth == 2) {
+            if (tag == "application") {
+                inApplication = true;
+                keepTag = true;
+
+                String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+                        "backupAgent", &error);
+                if (agent.length() > 0) {
+                    addProguardKeepRule(keep, agent, pkg.string(),
+                            assFile->getPrintableSource(), tree.getLineNumber());
+                }
+            } else if (tag == "instrumentation") {
+                keepTag = true;
+            }
+        }
+        if (!keepTag && inApplication && depth == 3) {
+            if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
+                keepTag = true;
+            }
+        }
+        if (keepTag) {
+            String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+                    "name", &error);
+            if (error != "") {
+                fprintf(stderr, "ERROR: %s\n", error.string());
+                return -1;
+            }
+            if (name.length() > 0) {
+                addProguardKeepRule(keep, name, pkg.string(),
+                        assFile->getPrintableSource(), tree.getLineNumber());
+            }
+        }
+    }
+
+    return NO_ERROR;
+}
+
+struct NamespaceAttributePair {
+    const char* ns;
+    const char* attr;
+
+    NamespaceAttributePair(const char* n, const char* a) : ns(n), attr(a) {}
+    NamespaceAttributePair() : ns(NULL), attr(NULL) {}
+};
+
+status_t
+writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile,
+        const char* startTag, const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs)
+{
+    status_t err;
+    ResXMLTree tree;
+    size_t len;
+    ResXMLTree::event_code_t code;
+
+    err = parseXMLResource(layoutFile, &tree);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    tree.restart();
+
+    if (startTag != NULL) {
+        bool haveStart = false;
+        while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+            if (code != ResXMLTree::START_TAG) {
+                continue;
+            }
+            String8 tag(tree.getElementName(&len));
+            if (tag == startTag) {
+                haveStart = true;
+            }
+            break;
+        }
+        if (!haveStart) {
+            return NO_ERROR;
+        }
+    }
+
+    while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code != ResXMLTree::START_TAG) {
+            continue;
+        }
+        String8 tag(tree.getElementName(&len));
+
+        // If there is no '.', we'll assume that it's one of the built in names.
+        if (strchr(tag.string(), '.')) {
+            addProguardKeepRule(keep, tag, NULL,
+                    layoutFile->getPrintableSource(), tree.getLineNumber());
+        } else if (tagAttrPairs != NULL) {
+            ssize_t tagIndex = tagAttrPairs->indexOfKey(tag);
+            if (tagIndex >= 0) {
+                const Vector<NamespaceAttributePair>& nsAttrVector = tagAttrPairs->valueAt(tagIndex);
+                for (size_t i = 0; i < nsAttrVector.size(); i++) {
+                    const NamespaceAttributePair& nsAttr = nsAttrVector[i];
+
+                    ssize_t attrIndex = tree.indexOfAttribute(nsAttr.ns, nsAttr.attr);
+                    if (attrIndex < 0) {
+                        // fprintf(stderr, "%s:%d: <%s> does not have attribute %s:%s.\n",
+                        //        layoutFile->getPrintableSource().string(), tree.getLineNumber(),
+                        //        tag.string(), nsAttr.ns, nsAttr.attr);
+                    } else {
+                        size_t len;
+                        addProguardKeepRule(keep,
+                                            String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+                                            layoutFile->getPrintableSource(), tree.getLineNumber());
+                    }
+                }
+            }
+        }
+        ssize_t attrIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "onClick");
+        if (attrIndex >= 0) {
+            size_t len;
+            addProguardKeepMethodRule(keep,
+                                String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+                                layoutFile->getPrintableSource(), tree.getLineNumber());
+        }
+    }
+
+    return NO_ERROR;
+}
+
+static void addTagAttrPair(KeyedVector<String8, Vector<NamespaceAttributePair> >* dest,
+        const char* tag, const char* ns, const char* attr) {
+    String8 tagStr(tag);
+    ssize_t index = dest->indexOfKey(tagStr);
+
+    if (index < 0) {
+        Vector<NamespaceAttributePair> vector;
+        vector.add(NamespaceAttributePair(ns, attr));
+        dest->add(tagStr, vector);
+    } else {
+        dest->editValueAt(index).add(NamespaceAttributePair(ns, attr));
+    }
+}
+
+status_t
+writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
+{
+    status_t err;
+
+    // tag:attribute pairs that should be checked in layout files.
+    KeyedVector<String8, Vector<NamespaceAttributePair> > kLayoutTagAttrPairs;
+    addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class");
+    addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class");
+    addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name");
+
+    // tag:attribute pairs that should be checked in xml files.
+    KeyedVector<String8, Vector<NamespaceAttributePair> > kXmlTagAttrPairs;
+    addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment");
+    addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment");
+
+    const Vector<sp<AaptDir> >& dirs = assets->resDirs();
+    const size_t K = dirs.size();
+    for (size_t k=0; k<K; k++) {
+        const sp<AaptDir>& d = dirs.itemAt(k);
+        const String8& dirName = d->getLeaf();
+        const char* startTag = NULL;
+        const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs = NULL;
+        if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) {
+            tagAttrPairs = &kLayoutTagAttrPairs;
+        } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) {
+            startTag = "PreferenceScreen";
+            tagAttrPairs = &kXmlTagAttrPairs;
+        } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) {
+            startTag = "menu";
+            tagAttrPairs = NULL;
+        } else {
+            continue;
+        }
+
+        const KeyedVector<String8,sp<AaptGroup> > groups = d->getFiles();
+        const size_t N = groups.size();
+        for (size_t i=0; i<N; i++) {
+            const sp<AaptGroup>& group = groups.valueAt(i);
+            const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles();
+            const size_t M = files.size();
+            for (size_t j=0; j<M; j++) {
+                err = writeProguardForXml(keep, files.valueAt(j), startTag, tagAttrPairs);
+                if (err < 0) {
+                    return err;
+                }
+            }
+        }
+    }
+    // Handle the overlays
+    sp<AaptAssets> overlay = assets->getOverlay();
+    if (overlay.get()) {
+        return writeProguardForLayouts(keep, overlay);
+    }
+
+    return NO_ERROR;
+}
+
+status_t
+writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+    status_t err = -1;
+
+    if (!bundle->getProguardFile()) {
+        return NO_ERROR;
+    }
+
+    ProguardKeepSet keep;
+
+    err = writeProguardForAndroidManifest(&keep, assets);
+    if (err < 0) {
+        return err;
+    }
+
+    err = writeProguardForLayouts(&keep, assets);
+    if (err < 0) {
+        return err;
+    }
+
+    FILE* fp = fopen(bundle->getProguardFile(), "w+");
+    if (fp == NULL) {
+        fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
+                bundle->getProguardFile(), strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules;
+    const size_t N = rules.size();
+    for (size_t i=0; i<N; i++) {
+        const SortedVector<String8>& locations = rules.valueAt(i);
+        const size_t M = locations.size();
+        for (size_t j=0; j<M; j++) {
+            fprintf(fp, "# %s\n", locations.itemAt(j).string());
+        }
+        fprintf(fp, "%s\n\n", rules.keyAt(i).string());
+    }
+    fclose(fp);
+
+    return err;
+}
+
+// Loops through the string paths and writes them to the file pointer
+// Each file path is written on its own line with a terminating backslash.
+status_t writePathsToFile(const sp<FilePathStore>& files, FILE* fp)
+{
+    status_t deps = -1;
+    for (size_t file_i = 0; file_i < files->size(); ++file_i) {
+        // Add the full file path to the dependency file
+        fprintf(fp, "%s \\\n", files->itemAt(file_i).string());
+        deps++;
+    }
+    return deps;
+}
+
+status_t
+writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets, FILE* fp, bool includeRaw)
+{
+    status_t deps = -1;
+    deps += writePathsToFile(assets->getFullResPaths(), fp);
+    if (includeRaw) {
+        deps += writePathsToFile(assets->getFullAssetPaths(), fp);
+    }
+    return deps;
+}
diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp
new file mode 100644
index 0000000..8cfd2a5
--- /dev/null
+++ b/tools/aapt/ResourceFilter.cpp
@@ -0,0 +1,112 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "ResourceFilter.h"
+
+status_t
+ResourceFilter::parse(const char* arg)
+{
+    if (arg == NULL) {
+        return 0;
+    }
+
+    const char* p = arg;
+    const char* q;
+
+    while (true) {
+        q = strchr(p, ',');
+        if (q == NULL) {
+            q = p + strlen(p);
+        }
+
+        String8 part(p, q-p);
+
+        if (part == "zz_ZZ") {
+            mContainsPseudo = true;
+        }
+        int axis;
+        uint32_t value;
+        if (AaptGroupEntry::parseNamePart(part, &axis, &value)) {
+            fprintf(stderr, "Invalid configuration: %s\n", arg);
+            fprintf(stderr, "                       ");
+            for (int i=0; i<p-arg; i++) {
+                fprintf(stderr, " ");
+            }
+            for (int i=0; i<q-p; i++) {
+                fprintf(stderr, "^");
+            }
+            fprintf(stderr, "\n");
+            return 1;
+        }
+
+        ssize_t index = mData.indexOfKey(axis);
+        if (index < 0) {
+            mData.add(axis, SortedVector<uint32_t>());
+        }
+        SortedVector<uint32_t>& sv = mData.editValueFor(axis);
+        sv.add(value);
+        // if it's a locale with a region, also match an unmodified locale of the
+        // same language
+        if (axis == AXIS_LANGUAGE) {
+            if (value & 0xffff0000) {
+                sv.add(value & 0x0000ffff);
+            }
+        }
+        p = q;
+        if (!*p) break;
+        p++;
+    }
+
+    return NO_ERROR;
+}
+
+bool
+ResourceFilter::isEmpty() const
+{
+    return mData.size() == 0;
+}
+
+bool
+ResourceFilter::match(int axis, uint32_t value) const
+{
+    if (value == 0) {
+        // they didn't specify anything so take everything
+        return true;
+    }
+    ssize_t index = mData.indexOfKey(axis);
+    if (index < 0) {
+        // we didn't request anything on this axis so take everything
+        return true;
+    }
+    const SortedVector<uint32_t>& sv = mData.valueAt(index);
+    return sv.indexOf(value) >= 0;
+}
+
+bool
+ResourceFilter::match(int axis, const ResTable_config& config) const
+{
+    return match(axis, AaptGroupEntry::getConfigValueForAxis(config, axis));
+}
+
+bool
+ResourceFilter::match(const ResTable_config& config) const
+{
+    for (int i=AXIS_START; i<=AXIS_END; i++) {
+        if (!match(i, AaptGroupEntry::getConfigValueForAxis(config, i))) {
+            return false;
+        }
+    }
+    return true;
+}
+
+const SortedVector<uint32_t>* ResourceFilter::configsForAxis(int axis) const
+{
+    ssize_t index = mData.indexOfKey(axis);
+    if (index < 0) {
+        return NULL;
+    }
+    return &mData.valueAt(index);
+}
diff --git a/tools/aapt/ResourceFilter.h b/tools/aapt/ResourceFilter.h
new file mode 100644
index 0000000..647b7bb
--- /dev/null
+++ b/tools/aapt/ResourceFilter.h
@@ -0,0 +1,33 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef RESOURCE_FILTER_H
+#define RESOURCE_FILTER_H
+
+#include "AaptAssets.h"
+
+/**
+ * Implements logic for parsing and handling "-c" and "--preferred-configurations"
+ * options.
+ */
+class ResourceFilter
+{
+public:
+    ResourceFilter() : mData(), mContainsPseudo(false) {}
+    status_t parse(const char* arg);
+    bool isEmpty() const;
+    bool match(int axis, uint32_t value) const;
+    bool match(int axis, const ResTable_config& config) const;
+    bool match(const ResTable_config& config) const;
+    const SortedVector<uint32_t>* configsForAxis(int axis) const;
+    inline bool containsPseudo() const { return mContainsPseudo; }
+
+private:
+    KeyedVector<int,SortedVector<uint32_t> > mData;
+    bool mContainsPseudo;
+};
+
+#endif
diff --git a/tools/aapt/ResourceIdCache.cpp b/tools/aapt/ResourceIdCache.cpp
new file mode 100644
index 0000000..e03f4f6
--- /dev/null
+++ b/tools/aapt/ResourceIdCache.cpp
@@ -0,0 +1,107 @@
+//
+// Copyright 2012 The Android Open Source Project
+//
+// Manage a resource ID cache.
+
+#define LOG_TAG "ResourceIdCache"
+
+#include <utils/String16.h>
+#include <utils/Log.h>
+#include "ResourceIdCache.h"
+#include <map>
+using namespace std;
+
+
+static size_t mHits = 0;
+static size_t mMisses = 0;
+static size_t mCollisions = 0;
+
+static const size_t MAX_CACHE_ENTRIES = 2048;
+static const android::String16 TRUE16("1");
+static const android::String16 FALSE16("0");
+
+struct CacheEntry {
+    // concatenation of the relevant strings into a single instance
+    android::String16 hashedName;
+    uint32_t id;
+
+    CacheEntry() {}
+    CacheEntry(const android::String16& name, uint32_t resId) : hashedName(name), id(resId) { }
+};
+
+static map< uint32_t, CacheEntry > mIdMap;
+
+
+// djb2; reasonable choice for strings when collisions aren't particularly important
+static inline uint32_t hashround(uint32_t hash, int c) {
+    return ((hash << 5) + hash) + c;    /* hash * 33 + c */
+}
+
+static uint32_t hash(const android::String16& hashableString) {
+    uint32_t hash = 5381;
+    const char16_t* str = hashableString.string();
+    while (int c = *str++) hash = hashround(hash, c);
+    return hash;
+}
+
+namespace android {
+
+static inline String16 makeHashableName(const android::String16& package,
+        const android::String16& type,
+        const android::String16& name,
+        bool onlyPublic) {
+    String16 hashable = String16(name);
+    hashable += type;
+    hashable += package;
+    hashable += (onlyPublic ? TRUE16 : FALSE16);
+    return hashable;
+}
+
+uint32_t ResourceIdCache::lookup(const android::String16& package,
+        const android::String16& type,
+        const android::String16& name,
+        bool onlyPublic) {
+    const String16 hashedName = makeHashableName(package, type, name, onlyPublic);
+    const uint32_t hashcode = hash(hashedName);
+    map<uint32_t, CacheEntry>::iterator item = mIdMap.find(hashcode);
+    if (item == mIdMap.end()) {
+        // cache miss
+        mMisses++;
+        return 0;
+    }
+
+    // legit match?
+    if (hashedName == (*item).second.hashedName) {
+        mHits++;
+        return (*item).second.id;
+    }
+
+    // collision
+    mCollisions++;
+    mIdMap.erase(hashcode);
+    return 0;
+}
+
+// returns the resource ID being stored, for callsite convenience
+uint32_t ResourceIdCache::store(const android::String16& package,
+        const android::String16& type,
+        const android::String16& name,
+        bool onlyPublic,
+        uint32_t resId) {
+    if (mIdMap.size() < MAX_CACHE_ENTRIES) {
+        const String16 hashedName = makeHashableName(package, type, name, onlyPublic);
+        const uint32_t hashcode = hash(hashedName);
+        mIdMap[hashcode] = CacheEntry(hashedName, resId);
+    }
+    return resId;
+}
+
+void ResourceIdCache::dump() {
+    printf("ResourceIdCache dump:\n");
+    printf("Size: %ld\n", mIdMap.size());
+    printf("Hits:   %ld\n", mHits);
+    printf("Misses: %ld\n", mMisses);
+    printf("(Collisions: %ld)\n", mCollisions);
+}
+
+}
diff --git a/tools/aapt/ResourceIdCache.h b/tools/aapt/ResourceIdCache.h
new file mode 100644
index 0000000..65f7781
--- /dev/null
+++ b/tools/aapt/ResourceIdCache.h
@@ -0,0 +1,30 @@
+//
+// Copyright 2012 The Android Open Source Project
+//
+// Manage a resource ID cache.
+
+#ifndef RESOURCE_ID_CACHE_H
+#define RESOURCE_ID_CACHE_H
+
+namespace android {
+class android::String16;
+
+class ResourceIdCache {
+public:
+    static uint32_t lookup(const android::String16& package,
+            const android::String16& type,
+            const android::String16& name,
+            bool onlyPublic);
+
+    static uint32_t store(const android::String16& package,
+            const android::String16& type,
+            const android::String16& name,
+            bool onlyPublic,
+            uint32_t resId);
+
+    static void dump(void);
+};
+
+}
+
+#endif
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
new file mode 100644
index 0000000..52ebaf0
--- /dev/null
+++ b/tools/aapt/ResourceTable.cpp
@@ -0,0 +1,3905 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "ResourceTable.h"
+
+#include "XMLNode.h"
+#include "ResourceFilter.h"
+#include "ResourceIdCache.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/ByteOrder.h>
+#include <stdarg.h>
+
+#define NOISY(x) //x
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+                        const sp<AaptFile>& target,
+                        ResourceTable* table,
+                        int options)
+{
+    sp<XMLNode> root = XMLNode::parse(target);
+    if (root == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    
+    return compileXmlFile(assets, root, target, table, options);
+}
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+                        const sp<AaptFile>& target,
+                        const sp<AaptFile>& outTarget,
+                        ResourceTable* table,
+                        int options)
+{
+    sp<XMLNode> root = XMLNode::parse(target);
+    if (root == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    
+    return compileXmlFile(assets, root, outTarget, table, options);
+}
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+                        const sp<XMLNode>& root,
+                        const sp<AaptFile>& target,
+                        ResourceTable* table,
+                        int options)
+{
+    if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
+        root->removeWhitespace(true, NULL);
+    } else  if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
+        root->removeWhitespace(false, NULL);
+    }
+
+    if ((options&XML_COMPILE_UTF8) != 0) {
+        root->setUTF8(true);
+    }
+
+    bool hasErrors = false;
+    
+    if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
+        status_t err = root->assignResourceIds(assets, table);
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    status_t err = root->parseValues(assets, table);
+    if (err != NO_ERROR) {
+        hasErrors = true;
+    }
+
+    if (hasErrors) {
+        return UNKNOWN_ERROR;
+    }
+    
+    NOISY(printf("Input XML Resource:\n"));
+    NOISY(root->print());
+    err = root->flatten(target,
+            (options&XML_COMPILE_STRIP_COMMENTS) != 0,
+            (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    NOISY(printf("Output XML Resource:\n"));
+    NOISY(ResXMLTree tree;
+        tree.setTo(target->getData(), target->getSize());
+        printXMLBlock(&tree));
+
+    target->setCompressionMethod(ZipEntry::kCompressDeflated);
+    
+    return err;
+}
+
+#undef NOISY
+#define NOISY(x) //x
+
+struct flag_entry
+{
+    const char16_t* name;
+    size_t nameLen;
+    uint32_t value;
+    const char* description;
+};
+
+static const char16_t referenceArray[] =
+    { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' };
+static const char16_t stringArray[] =
+    { 's', 't', 'r', 'i', 'n', 'g' };
+static const char16_t integerArray[] =
+    { 'i', 'n', 't', 'e', 'g', 'e', 'r' };
+static const char16_t booleanArray[] =
+    { 'b', 'o', 'o', 'l', 'e', 'a', 'n' };
+static const char16_t colorArray[] =
+    { 'c', 'o', 'l', 'o', 'r' };
+static const char16_t floatArray[] =
+    { 'f', 'l', 'o', 'a', 't' };
+static const char16_t dimensionArray[] =
+    { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' };
+static const char16_t fractionArray[] =
+    { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' };
+static const char16_t enumArray[] =
+    { 'e', 'n', 'u', 'm' };
+static const char16_t flagsArray[] =
+    { 'f', 'l', 'a', 'g', 's' };
+
+static const flag_entry gFormatFlags[] = {
+    { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE,
+      "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n"
+      "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."},
+    { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING,
+      "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." },
+    { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER,
+      "an integer value, such as \"<code>100</code>\"." },
+    { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN,
+      "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." },
+    { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR,
+      "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n"
+      "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." },
+    { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT,
+      "a floating point value, such as \"<code>1.2</code>\"."},
+    { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
+      "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
+      "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
+      "in (inches), mm (millimeters)." },
+    { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
+      "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
+      "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n"
+      "some parent container." },
+    { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL },
+    { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL },
+    { NULL, 0, 0, NULL }
+};
+
+static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' };
+
+static const flag_entry l10nRequiredFlags[] = {
+    { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL },
+    { NULL, 0, 0, NULL }
+};
+
+static const char16_t nulStr[] = { 0 };
+
+static uint32_t parse_flags(const char16_t* str, size_t len,
+                             const flag_entry* flags, bool* outError = NULL)
+{
+    while (len > 0 && isspace(*str)) {
+        str++;
+        len--;
+    }
+    while (len > 0 && isspace(str[len-1])) {
+        len--;
+    }
+
+    const char16_t* const end = str + len;
+    uint32_t value = 0;
+
+    while (str < end) {
+        const char16_t* div = str;
+        while (div < end && *div != '|') {
+            div++;
+        }
+
+        const flag_entry* cur = flags;
+        while (cur->name) {
+            if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) {
+                value |= cur->value;
+                break;
+            }
+            cur++;
+        }
+
+        if (!cur->name) {
+            if (outError) *outError = true;
+            return 0;
+        }
+
+        str = div < end ? div+1 : div;
+    }
+
+    if (outError) *outError = false;
+    return value;
+}
+
+static String16 mayOrMust(int type, int flags)
+{
+    if ((type&(~flags)) == 0) {
+        return String16("<p>Must");
+    }
+    
+    return String16("<p>May");
+}
+
+static void appendTypeInfo(ResourceTable* outTable, const String16& pkg,
+        const String16& typeName, const String16& ident, int type,
+        const flag_entry* flags)
+{
+    bool hadType = false;
+    while (flags->name) {
+        if ((type&flags->value) != 0 && flags->description != NULL) {
+            String16 fullMsg(mayOrMust(type, flags->value));
+            fullMsg.append(String16(" be "));
+            fullMsg.append(String16(flags->description));
+            outTable->appendTypeComment(pkg, typeName, ident, fullMsg);
+            hadType = true;
+        }
+        flags++;
+    }
+    if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) {
+        outTable->appendTypeComment(pkg, typeName, ident,
+                String16("<p>This may also be a reference to a resource (in the form\n"
+                         "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
+                         "theme attribute (in the form\n"
+                         "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
+                         "containing a value of this type."));
+    }
+}
+
+struct PendingAttribute
+{
+    const String16 myPackage;
+    const SourcePos sourcePos;
+    const bool appendComment;
+    int32_t type;
+    String16 ident;
+    String16 comment;
+    bool hasErrors;
+    bool added;
+    
+    PendingAttribute(String16 _package, const sp<AaptFile>& in,
+            ResXMLTree& block, bool _appendComment)
+        : myPackage(_package)
+        , sourcePos(in->getPrintableSource(), block.getLineNumber())
+        , appendComment(_appendComment)
+        , type(ResTable_map::TYPE_ANY)
+        , hasErrors(false)
+        , added(false)
+    {
+    }
+    
+    status_t createIfNeeded(ResourceTable* outTable)
+    {
+        if (added || hasErrors) {
+            return NO_ERROR;
+        }
+        added = true;
+
+        String16 attr16("attr");
+        
+        if (outTable->hasBagOrEntry(myPackage, attr16, ident)) {
+            sourcePos.error("Attribute \"%s\" has already been defined\n",
+                    String8(ident).string());
+            hasErrors = true;
+            return UNKNOWN_ERROR;
+        }
+        
+        char numberStr[16];
+        sprintf(numberStr, "%d", type);
+        status_t err = outTable->addBag(sourcePos, myPackage,
+                attr16, ident, String16(""),
+                String16("^type"),
+                String16(numberStr), NULL, NULL);
+        if (err != NO_ERROR) {
+            hasErrors = true;
+            return err;
+        }
+        outTable->appendComment(myPackage, attr16, ident, comment, appendComment);
+        //printf("Attribute %s comment: %s\n", String8(ident).string(),
+        //     String8(comment).string());
+        return err;
+    }
+};
+
+static status_t compileAttribute(const sp<AaptFile>& in,
+                                 ResXMLTree& block,
+                                 const String16& myPackage,
+                                 ResourceTable* outTable,
+                                 String16* outIdent = NULL,
+                                 bool inStyleable = false)
+{
+    PendingAttribute attr(myPackage, in, block, inStyleable);
+    
+    const String16 attr16("attr");
+    const String16 id16("id");
+
+    // Attribute type constants.
+    const String16 enum16("enum");
+    const String16 flag16("flag");
+
+    ResXMLTree::event_code_t code;
+    size_t len;
+    status_t err;
+    
+    ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+    if (identIdx >= 0) {
+        attr.ident = String16(block.getAttributeStringValue(identIdx, &len));
+        if (outIdent) {
+            *outIdent = attr.ident;
+        }
+    } else {
+        attr.sourcePos.error("A 'name' attribute is required for <attr>\n");
+        attr.hasErrors = true;
+    }
+
+    attr.comment = String16(
+            block.getComment(&len) ? block.getComment(&len) : nulStr);
+
+    ssize_t typeIdx = block.indexOfAttribute(NULL, "format");
+    if (typeIdx >= 0) {
+        String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len));
+        attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags);
+        if (attr.type == 0) {
+            attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n",
+                    String8(typeStr).string());
+            attr.hasErrors = true;
+        }
+        attr.createIfNeeded(outTable);
+    } else if (!inStyleable) {
+        // Attribute definitions outside of styleables always define the
+        // attribute as a generic value.
+        attr.createIfNeeded(outTable);
+    }
+
+    //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type);
+
+    ssize_t minIdx = block.indexOfAttribute(NULL, "min");
+    if (minIdx >= 0) {
+        String16 val = String16(block.getAttributeStringValue(minIdx, &len));
+        if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
+            attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n",
+                    String8(val).string());
+            attr.hasErrors = true;
+        }
+        attr.createIfNeeded(outTable);
+        if (!attr.hasErrors) {
+            err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+                    String16(""), String16("^min"), String16(val), NULL, NULL);
+            if (err != NO_ERROR) {
+                attr.hasErrors = true;
+            }
+        }
+    }
+
+    ssize_t maxIdx = block.indexOfAttribute(NULL, "max");
+    if (maxIdx >= 0) {
+        String16 val = String16(block.getAttributeStringValue(maxIdx, &len));
+        if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
+            attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n",
+                    String8(val).string());
+            attr.hasErrors = true;
+        }
+        attr.createIfNeeded(outTable);
+        if (!attr.hasErrors) {
+            err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+                    String16(""), String16("^max"), String16(val), NULL, NULL);
+            attr.hasErrors = true;
+        }
+    }
+
+    if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) {
+        attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n");
+        attr.hasErrors = true;
+    }
+
+    ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization");
+    if (l10nIdx >= 0) {
+        const uint16_t* str = block.getAttributeStringValue(l10nIdx, &len);
+        bool error;
+        uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error);
+        if (error) {
+            attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n",
+                    String8(str).string());
+            attr.hasErrors = true;
+        }
+        attr.createIfNeeded(outTable);
+        if (!attr.hasErrors) {
+            char buf[11];
+            sprintf(buf, "%d", l10n_required);
+            err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+                    String16(""), String16("^l10n"), String16(buf), NULL, NULL);
+            if (err != NO_ERROR) {
+                attr.hasErrors = true;
+            }
+        }
+    }
+
+    String16 enumOrFlagsComment;
+    
+    while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::START_TAG) {
+            uint32_t localType = 0;
+            if (strcmp16(block.getElementName(&len), enum16.string()) == 0) {
+                localType = ResTable_map::TYPE_ENUM;
+            } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) {
+                localType = ResTable_map::TYPE_FLAGS;
+            } else {
+                SourcePos(in->getPrintableSource(), block.getLineNumber())
+                        .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n",
+                        String8(block.getElementName(&len)).string());
+                return UNKNOWN_ERROR;
+            }
+
+            attr.createIfNeeded(outTable);
+            
+            if (attr.type == ResTable_map::TYPE_ANY) {
+                // No type was explicitly stated, so supplying enum tags
+                // implicitly creates an enum or flag.
+                attr.type = 0;
+            }
+
+            if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) {
+                // Wasn't originally specified as an enum, so update its type.
+                attr.type |= localType;
+                if (!attr.hasErrors) {
+                    char numberStr[16];
+                    sprintf(numberStr, "%d", attr.type);
+                    err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+                            myPackage, attr16, attr.ident, String16(""),
+                            String16("^type"), String16(numberStr), NULL, NULL, true);
+                    if (err != NO_ERROR) {
+                        attr.hasErrors = true;
+                    }
+                }
+            } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) {
+                if (localType == ResTable_map::TYPE_ENUM) {
+                    SourcePos(in->getPrintableSource(), block.getLineNumber())
+                            .error("<enum> attribute can not be used inside a flags format\n");
+                    attr.hasErrors = true;
+                } else {
+                    SourcePos(in->getPrintableSource(), block.getLineNumber())
+                            .error("<flag> attribute can not be used inside a enum format\n");
+                    attr.hasErrors = true;
+                }
+            }
+
+            String16 itemIdent;
+            ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
+            if (itemIdentIdx >= 0) {
+                itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
+            } else {
+                SourcePos(in->getPrintableSource(), block.getLineNumber())
+                        .error("A 'name' attribute is required for <enum> or <flag>\n");
+                attr.hasErrors = true;
+            }
+
+            String16 value;
+            ssize_t valueIdx = block.indexOfAttribute(NULL, "value");
+            if (valueIdx >= 0) {
+                value = String16(block.getAttributeStringValue(valueIdx, &len));
+            } else {
+                SourcePos(in->getPrintableSource(), block.getLineNumber())
+                        .error("A 'value' attribute is required for <enum> or <flag>\n");
+                attr.hasErrors = true;
+            }
+            if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) {
+                SourcePos(in->getPrintableSource(), block.getLineNumber())
+                        .error("Tag <enum> or <flag> 'value' attribute must be a number,"
+                        " not \"%s\"\n",
+                        String8(value).string());
+                attr.hasErrors = true;
+            }
+
+            // Make sure an id is defined for this enum/flag identifier...
+            if (!attr.hasErrors && !outTable->hasBagOrEntry(itemIdent, &id16, &myPackage)) {
+                err = outTable->startBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+                                         myPackage, id16, itemIdent, String16(), NULL);
+                if (err != NO_ERROR) {
+                    attr.hasErrors = true;
+                }
+            }
+
+            if (!attr.hasErrors) {
+                if (enumOrFlagsComment.size() == 0) {
+                    enumOrFlagsComment.append(mayOrMust(attr.type,
+                            ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS));
+                    enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM)
+                                       ? String16(" be one of the following constant values.")
+                                       : String16(" be one or more (separated by '|') of the following constant values."));
+                    enumOrFlagsComment.append(String16("</p>\n<table>\n"
+                                                "<colgroup align=\"left\" />\n"
+                                                "<colgroup align=\"left\" />\n"
+                                                "<colgroup align=\"left\" />\n"
+                                                "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>"));
+                }
+                
+                enumOrFlagsComment.append(String16("\n<tr><td><code>"));
+                enumOrFlagsComment.append(itemIdent);
+                enumOrFlagsComment.append(String16("</code></td><td>"));
+                enumOrFlagsComment.append(value);
+                enumOrFlagsComment.append(String16("</td><td>"));
+                if (block.getComment(&len)) {
+                    enumOrFlagsComment.append(String16(block.getComment(&len)));
+                }
+                enumOrFlagsComment.append(String16("</td></tr>"));
+                
+                err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+                                       myPackage,
+                                       attr16, attr.ident, String16(""),
+                                       itemIdent, value, NULL, NULL, false, true);
+                if (err != NO_ERROR) {
+                    attr.hasErrors = true;
+                }
+            }
+        } else if (code == ResXMLTree::END_TAG) {
+            if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
+                break;
+            }
+            if ((attr.type&ResTable_map::TYPE_ENUM) != 0) {
+                if (strcmp16(block.getElementName(&len), enum16.string()) != 0) {
+                    SourcePos(in->getPrintableSource(), block.getLineNumber())
+                            .error("Found tag </%s> where </enum> is expected\n",
+                            String8(block.getElementName(&len)).string());
+                    return UNKNOWN_ERROR;
+                }
+            } else {
+                if (strcmp16(block.getElementName(&len), flag16.string()) != 0) {
+                    SourcePos(in->getPrintableSource(), block.getLineNumber())
+                            .error("Found tag </%s> where </flag> is expected\n",
+                            String8(block.getElementName(&len)).string());
+                    return UNKNOWN_ERROR;
+                }
+            }
+        }
+    }
+    
+    if (!attr.hasErrors && attr.added) {
+        appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags);
+    }
+    
+    if (!attr.hasErrors && enumOrFlagsComment.size() > 0) {
+        enumOrFlagsComment.append(String16("\n</table>"));
+        outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment);
+    }
+
+
+    return NO_ERROR;
+}
+
+bool localeIsDefined(const ResTable_config& config)
+{
+    return config.locale == 0;
+}
+
+status_t parseAndAddBag(Bundle* bundle,
+                        const sp<AaptFile>& in,
+                        ResXMLTree* block,
+                        const ResTable_config& config,
+                        const String16& myPackage,
+                        const String16& curType,
+                        const String16& ident,
+                        const String16& parentIdent,
+                        const String16& itemIdent,
+                        int32_t curFormat,
+                        bool isFormatted,
+                        const String16& product,
+                        bool pseudolocalize,
+                        const bool overwrite,
+                        ResourceTable* outTable)
+{
+    status_t err;
+    const String16 item16("item");
+    
+    String16 str;
+    Vector<StringPool::entry_style_span> spans;
+    err = parseStyledString(bundle, in->getPrintableSource().string(),
+                            block, item16, &str, &spans, isFormatted,
+                            pseudolocalize);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    
+    NOISY(printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d "
+                 " pid=%s, bag=%s, id=%s: %s\n",
+                 config.language[0], config.language[1],
+                 config.country[0], config.country[1],
+                 config.orientation, config.density,
+                 String8(parentIdent).string(),
+                 String8(ident).string(),
+                 String8(itemIdent).string(),
+                 String8(str).string()));
+
+    err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()),
+                           myPackage, curType, ident, parentIdent, itemIdent, str,
+                           &spans, &config, overwrite, false, curFormat);
+    return err;
+}
+
+/*
+ * Returns true if needle is one of the elements in the comma-separated list
+ * haystack, false otherwise.
+ */
+bool isInProductList(const String16& needle, const String16& haystack) {
+    const char16_t *needle2 = needle.string();
+    const char16_t *haystack2 = haystack.string();
+    size_t needlesize = needle.size();
+
+    while (*haystack2 != '\0') {
+        if (strncmp16(haystack2, needle2, needlesize) == 0) {
+            if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') {
+                return true;
+            }
+        }
+
+        while (*haystack2 != '\0' && *haystack2 != ',') {
+            haystack2++;
+        }
+        if (*haystack2 == ',') {
+            haystack2++;
+        }
+    }
+
+    return false;
+}
+
+status_t parseAndAddEntry(Bundle* bundle,
+                        const sp<AaptFile>& in,
+                        ResXMLTree* block,
+                        const ResTable_config& config,
+                        const String16& myPackage,
+                        const String16& curType,
+                        const String16& ident,
+                        const String16& curTag,
+                        bool curIsStyled,
+                        int32_t curFormat,
+                        bool isFormatted,
+                        const String16& product,
+                        bool pseudolocalize,
+                        const bool overwrite,
+                        ResourceTable* outTable)
+{
+    status_t err;
+
+    String16 str;
+    Vector<StringPool::entry_style_span> spans;
+    err = parseStyledString(bundle, in->getPrintableSource().string(), block,
+                            curTag, &str, curIsStyled ? &spans : NULL,
+                            isFormatted, pseudolocalize);
+
+    if (err < NO_ERROR) { 
+        return err;
+    }
+
+    /*
+     * If a product type was specified on the command line
+     * and also in the string, and the two are not the same,
+     * return without adding the string.
+     */
+
+    const char *bundleProduct = bundle->getProduct();
+    if (bundleProduct == NULL) {
+        bundleProduct = "";
+    }
+
+    if (product.size() != 0) {
+        /*
+         * If the command-line-specified product is empty, only "default"
+         * matches.  Other variants are skipped.  This is so generation
+         * of the R.java file when the product is not known is predictable.
+         */
+
+        if (bundleProduct[0] == '\0') {
+            if (strcmp16(String16("default").string(), product.string()) != 0) {
+                return NO_ERROR;
+            }
+        } else {
+            /*
+             * The command-line product is not empty.
+             * If the product for this string is on the command-line list,
+             * it matches.  "default" also matches, but only if nothing
+             * else has matched already.
+             */
+
+            if (isInProductList(product, String16(bundleProduct))) {
+                ;
+            } else if (strcmp16(String16("default").string(), product.string()) == 0 &&
+                       !outTable->hasBagOrEntry(myPackage, curType, ident, config)) {
+                ;
+            } else {
+                return NO_ERROR;
+            }
+        }
+    }
+
+    NOISY(printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n",
+                 config.language[0], config.language[1],
+                 config.country[0], config.country[1],
+                 config.orientation, config.density,
+                 String8(ident).string(), String8(str).string()));
+
+    err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()),
+                             myPackage, curType, ident, str, &spans, &config,
+                             false, curFormat, overwrite);
+
+    return err;
+}
+
+status_t compileResourceFile(Bundle* bundle,
+                             const sp<AaptAssets>& assets,
+                             const sp<AaptFile>& in,
+                             const ResTable_config& defParams,
+                             const bool overwrite,
+                             ResourceTable* outTable)
+{
+    ResXMLTree block;
+    status_t err = parseXMLResource(in, &block, false, true);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    // Top-level tag.
+    const String16 resources16("resources");
+
+    // Identifier declaration tags.
+    const String16 declare_styleable16("declare-styleable");
+    const String16 attr16("attr");
+
+    // Data creation organizational tags.
+    const String16 string16("string");
+    const String16 drawable16("drawable");
+    const String16 color16("color");
+    const String16 bool16("bool");
+    const String16 integer16("integer");
+    const String16 dimen16("dimen");
+    const String16 fraction16("fraction");
+    const String16 style16("style");
+    const String16 plurals16("plurals");
+    const String16 array16("array");
+    const String16 string_array16("string-array");
+    const String16 integer_array16("integer-array");
+    const String16 public16("public");
+    const String16 public_padding16("public-padding");
+    const String16 private_symbols16("private-symbols");
+    const String16 java_symbol16("java-symbol");
+    const String16 add_resource16("add-resource");
+    const String16 skip16("skip");
+    const String16 eat_comment16("eat-comment");
+
+    // Data creation tags.
+    const String16 bag16("bag");
+    const String16 item16("item");
+
+    // Attribute type constants.
+    const String16 enum16("enum");
+
+    // plural values
+    const String16 other16("other");
+    const String16 quantityOther16("^other");
+    const String16 zero16("zero");
+    const String16 quantityZero16("^zero");
+    const String16 one16("one");
+    const String16 quantityOne16("^one");
+    const String16 two16("two");
+    const String16 quantityTwo16("^two");
+    const String16 few16("few");
+    const String16 quantityFew16("^few");
+    const String16 many16("many");
+    const String16 quantityMany16("^many");
+
+    // useful attribute names and special values
+    const String16 name16("name");
+    const String16 translatable16("translatable");
+    const String16 formatted16("formatted");
+    const String16 false16("false");
+
+    const String16 myPackage(assets->getPackage());
+
+    bool hasErrors = false;
+
+    bool fileIsTranslatable = true;
+    if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) {
+        fileIsTranslatable = false;
+    }
+
+    DefaultKeyedVector<String16, uint32_t> nextPublicId(0);
+
+    ResXMLTree::event_code_t code;
+    do {
+        code = block.next();
+    } while (code == ResXMLTree::START_NAMESPACE);
+
+    size_t len;
+    if (code != ResXMLTree::START_TAG) {
+        SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                "No start tag found\n");
+        return UNKNOWN_ERROR;
+    }
+    if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
+        SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                "Invalid start tag %s\n", String8(block.getElementName(&len)).string());
+        return UNKNOWN_ERROR;
+    }
+
+    ResTable_config curParams(defParams);
+
+    ResTable_config pseudoParams(curParams);
+        pseudoParams.language[0] = 'z';
+        pseudoParams.language[1] = 'z';
+        pseudoParams.country[0] = 'Z';
+        pseudoParams.country[1] = 'Z';
+
+    while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::START_TAG) {
+            const String16* curTag = NULL;
+            String16 curType;
+            int32_t curFormat = ResTable_map::TYPE_ANY;
+            bool curIsBag = false;
+            bool curIsBagReplaceOnOverwrite = false;
+            bool curIsStyled = false;
+            bool curIsPseudolocalizable = false;
+            bool curIsFormatted = fileIsTranslatable;
+            bool localHasErrors = false;
+
+            if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+                        && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+                            break;
+                        }
+                    }
+                }
+                continue;
+
+            } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+                        && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+                            break;
+                        }
+                    }
+                }
+                continue;
+
+            } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
+                SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+            
+                String16 type;
+                ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+                if (typeIdx < 0) {
+                    srcPos.error("A 'type' attribute is required for <public>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                type = String16(block.getAttributeStringValue(typeIdx, &len));
+
+                String16 name;
+                ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+                if (nameIdx < 0) {
+                    srcPos.error("A 'name' attribute is required for <public>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+                uint32_t ident = 0;
+                ssize_t identIdx = block.indexOfAttribute(NULL, "id");
+                if (identIdx >= 0) {
+                    const char16_t* identStr = block.getAttributeStringValue(identIdx, &len);
+                    Res_value identValue;
+                    if (!ResTable::stringToInt(identStr, len, &identValue)) {
+                        srcPos.error("Given 'id' attribute is not an integer: %s\n",
+                                String8(block.getAttributeStringValue(identIdx, &len)).string());
+                        hasErrors = localHasErrors = true;
+                    } else {
+                        ident = identValue.data;
+                        nextPublicId.replaceValueFor(type, ident+1);
+                    }
+                } else if (nextPublicId.indexOfKey(type) < 0) {
+                    srcPos.error("No 'id' attribute supplied <public>,"
+                            " and no previous id defined in this file.\n");
+                    hasErrors = localHasErrors = true;
+                } else if (!localHasErrors) {
+                    ident = nextPublicId.valueFor(type);
+                    nextPublicId.replaceValueFor(type, ident+1);
+                }
+
+                if (!localHasErrors) {
+                    err = outTable->addPublic(srcPos, myPackage, type, name, ident);
+                    if (err < NO_ERROR) {
+                        hasErrors = localHasErrors = true;
+                    }
+                }
+                if (!localHasErrors) {
+                    sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+                    if (symbols != NULL) {
+                        symbols = symbols->addNestedSymbol(String8(type), srcPos);
+                    }
+                    if (symbols != NULL) {
+                        symbols->makeSymbolPublic(String8(name), srcPos);
+                        String16 comment(
+                            block.getComment(&len) ? block.getComment(&len) : nulStr);
+                        symbols->appendComment(String8(name), comment, srcPos);
+                    } else {
+                        srcPos.error("Unable to create symbols!\n");
+                        hasErrors = localHasErrors = true;
+                    }
+                }
+
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
+                            break;
+                        }
+                    }
+                }
+                continue;
+
+            } else if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
+                SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+            
+                String16 type;
+                ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+                if (typeIdx < 0) {
+                    srcPos.error("A 'type' attribute is required for <public-padding>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                type = String16(block.getAttributeStringValue(typeIdx, &len));
+
+                String16 name;
+                ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+                if (nameIdx < 0) {
+                    srcPos.error("A 'name' attribute is required for <public-padding>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+                uint32_t start = 0;
+                ssize_t startIdx = block.indexOfAttribute(NULL, "start");
+                if (startIdx >= 0) {
+                    const char16_t* startStr = block.getAttributeStringValue(startIdx, &len);
+                    Res_value startValue;
+                    if (!ResTable::stringToInt(startStr, len, &startValue)) {
+                        srcPos.error("Given 'start' attribute is not an integer: %s\n",
+                                String8(block.getAttributeStringValue(startIdx, &len)).string());
+                        hasErrors = localHasErrors = true;
+                    } else {
+                        start = startValue.data;
+                    }
+                } else if (nextPublicId.indexOfKey(type) < 0) {
+                    srcPos.error("No 'start' attribute supplied <public-padding>,"
+                            " and no previous id defined in this file.\n");
+                    hasErrors = localHasErrors = true;
+                } else if (!localHasErrors) {
+                    start = nextPublicId.valueFor(type);
+                }
+
+                uint32_t end = 0;
+                ssize_t endIdx = block.indexOfAttribute(NULL, "end");
+                if (endIdx >= 0) {
+                    const char16_t* endStr = block.getAttributeStringValue(endIdx, &len);
+                    Res_value endValue;
+                    if (!ResTable::stringToInt(endStr, len, &endValue)) {
+                        srcPos.error("Given 'end' attribute is not an integer: %s\n",
+                                String8(block.getAttributeStringValue(endIdx, &len)).string());
+                        hasErrors = localHasErrors = true;
+                    } else {
+                        end = endValue.data;
+                    }
+                } else {
+                    srcPos.error("No 'end' attribute supplied <public-padding>\n");
+                    hasErrors = localHasErrors = true;
+                }
+
+                if (end >= start) {
+                    nextPublicId.replaceValueFor(type, end+1);
+                } else {
+                    srcPos.error("Padding start '%ul' is after end '%ul'\n",
+                            start, end);
+                    hasErrors = localHasErrors = true;
+                }
+                
+                String16 comment(
+                    block.getComment(&len) ? block.getComment(&len) : nulStr);
+                for (uint32_t curIdent=start; curIdent<=end; curIdent++) {
+                    if (localHasErrors) {
+                        break;
+                    }
+                    String16 curName(name);
+                    char buf[64];
+                    sprintf(buf, "%d", (int)(end-curIdent+1));
+                    curName.append(String16(buf));
+                    
+                    err = outTable->addEntry(srcPos, myPackage, type, curName,
+                                             String16("padding"), NULL, &curParams, false,
+                                             ResTable_map::TYPE_STRING, overwrite);
+                    if (err < NO_ERROR) {
+                        hasErrors = localHasErrors = true;
+                        break;
+                    }
+                    err = outTable->addPublic(srcPos, myPackage, type,
+                            curName, curIdent);
+                    if (err < NO_ERROR) {
+                        hasErrors = localHasErrors = true;
+                        break;
+                    }
+                    sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+                    if (symbols != NULL) {
+                        symbols = symbols->addNestedSymbol(String8(type), srcPos);
+                    }
+                    if (symbols != NULL) {
+                        symbols->makeSymbolPublic(String8(curName), srcPos);
+                        symbols->appendComment(String8(curName), comment, srcPos);
+                    } else {
+                        srcPos.error("Unable to create symbols!\n");
+                        hasErrors = localHasErrors = true;
+                    }
+                }
+
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
+                            break;
+                        }
+                    }
+                }
+                continue;
+
+            } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
+                String16 pkg;
+                ssize_t pkgIdx = block.indexOfAttribute(NULL, "package");
+                if (pkgIdx < 0) {
+                    SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                            "A 'package' attribute is required for <private-symbols>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                pkg = String16(block.getAttributeStringValue(pkgIdx, &len));
+                if (!localHasErrors) {
+                    assets->setSymbolsPrivatePackage(String8(pkg));
+                }
+
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
+                            break;
+                        }
+                    }
+                }
+                continue;
+
+            } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
+                SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+            
+                String16 type;
+                ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+                if (typeIdx < 0) {
+                    srcPos.error("A 'type' attribute is required for <public>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                type = String16(block.getAttributeStringValue(typeIdx, &len));
+
+                String16 name;
+                ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+                if (nameIdx < 0) {
+                    srcPos.error("A 'name' attribute is required for <public>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+                sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R"));
+                if (symbols != NULL) {
+                    symbols = symbols->addNestedSymbol(String8(type), srcPos);
+                }
+                if (symbols != NULL) {
+                    symbols->makeSymbolJavaSymbol(String8(name), srcPos);
+                    String16 comment(
+                        block.getComment(&len) ? block.getComment(&len) : nulStr);
+                    symbols->appendComment(String8(name), comment, srcPos);
+                } else {
+                    srcPos.error("Unable to create symbols!\n");
+                    hasErrors = localHasErrors = true;
+                }
+
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
+                            break;
+                        }
+                    }
+                }
+                continue;
+
+
+            } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
+                SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+            
+                String16 typeName;
+                ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+                if (typeIdx < 0) {
+                    srcPos.error("A 'type' attribute is required for <add-resource>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                typeName = String16(block.getAttributeStringValue(typeIdx, &len));
+
+                String16 name;
+                ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+                if (nameIdx < 0) {
+                    srcPos.error("A 'name' attribute is required for <add-resource>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+                outTable->canAddEntry(srcPos, myPackage, typeName, name);
+
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
+                            break;
+                        }
+                    }
+                }
+                continue;
+                
+            } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
+                SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+                                
+                String16 ident;
+                ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+                if (identIdx < 0) {
+                    srcPos.error("A 'name' attribute is required for <declare-styleable>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                ident = String16(block.getAttributeStringValue(identIdx, &len));
+
+                sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+                if (!localHasErrors) {
+                    if (symbols != NULL) {
+                        symbols = symbols->addNestedSymbol(String8("styleable"), srcPos);
+                    }
+                    sp<AaptSymbols> styleSymbols = symbols;
+                    if (symbols != NULL) {
+                        symbols = symbols->addNestedSymbol(String8(ident), srcPos);
+                    }
+                    if (symbols == NULL) {
+                        srcPos.error("Unable to create symbols!\n");
+                        return UNKNOWN_ERROR;
+                    }
+                    
+                    String16 comment(
+                        block.getComment(&len) ? block.getComment(&len) : nulStr);
+                    styleSymbols->appendComment(String8(ident), comment, srcPos);
+                } else {
+                    symbols = NULL;
+                }
+
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::START_TAG) {
+                        if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+                            while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+                                   && code != ResXMLTree::BAD_DOCUMENT) {
+                                if (code == ResXMLTree::END_TAG) {
+                                    if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+                                        break;
+                                    }
+                                }
+                            }
+                            continue;
+                        } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+                            while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+                                   && code != ResXMLTree::BAD_DOCUMENT) {
+                                if (code == ResXMLTree::END_TAG) {
+                                    if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+                                        break;
+                                    }
+                                }
+                            }
+                            continue;
+                        } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) {
+                            SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                    "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n",
+                                    String8(block.getElementName(&len)).string());
+                            return UNKNOWN_ERROR;
+                        }
+
+                        String16 comment(
+                            block.getComment(&len) ? block.getComment(&len) : nulStr);
+                        String16 itemIdent;
+                        err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true);
+                        if (err != NO_ERROR) {
+                            hasErrors = localHasErrors = true;
+                        }
+
+                        if (symbols != NULL) {
+                            SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber());
+                            symbols->addSymbol(String8(itemIdent), 0, srcPos);
+                            symbols->appendComment(String8(itemIdent), comment, srcPos);
+                            //printf("Attribute %s comment: %s\n", String8(itemIdent).string(),
+                            //     String8(comment).string());
+                        }
+                    } else if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
+                            break;
+                        }
+
+                        SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                "Found tag </%s> where </attr> is expected\n",
+                                String8(block.getElementName(&len)).string());
+                        return UNKNOWN_ERROR;
+                    }
+                }
+                continue;
+
+            } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
+                err = compileAttribute(in, block, myPackage, outTable, NULL);
+                if (err != NO_ERROR) {
+                    hasErrors = true;
+                }
+                continue;
+
+            } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) {
+                curTag = &item16;
+                ssize_t attri = block.indexOfAttribute(NULL, "type");
+                if (attri >= 0) {
+                    curType = String16(block.getAttributeStringValue(attri, &len));
+                    ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
+                    if (formatIdx >= 0) {
+                        String16 formatStr = String16(block.getAttributeStringValue(
+                                formatIdx, &len));
+                        curFormat = parse_flags(formatStr.string(), formatStr.size(),
+                                                gFormatFlags);
+                        if (curFormat == 0) {
+                            SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                    "Tag <item> 'format' attribute value \"%s\" not valid\n",
+                                    String8(formatStr).string());
+                            hasErrors = localHasErrors = true;
+                        }
+                    }
+                } else {
+                    SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                            "A 'type' attribute is required for <item>\n");
+                    hasErrors = localHasErrors = true;
+                }
+                curIsStyled = true;
+            } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) {
+                // Note the existence and locale of every string we process
+                char rawLocale[16];
+                curParams.getLocale(rawLocale);
+                String8 locale(rawLocale);
+                String16 name;
+                String16 translatable;
+                String16 formatted;
+
+                size_t n = block.getAttributeCount();
+                for (size_t i = 0; i < n; i++) {
+                    size_t length;
+                    const uint16_t* attr = block.getAttributeName(i, &length);
+                    if (strcmp16(attr, name16.string()) == 0) {
+                        name.setTo(block.getAttributeStringValue(i, &length));
+                    } else if (strcmp16(attr, translatable16.string()) == 0) {
+                        translatable.setTo(block.getAttributeStringValue(i, &length));
+                    } else if (strcmp16(attr, formatted16.string()) == 0) {
+                        formatted.setTo(block.getAttributeStringValue(i, &length));
+                    }
+                }
+                
+                if (name.size() > 0) {
+                    if (translatable == false16) {
+                        curIsFormatted = false;
+                        // Untranslatable strings must only exist in the default [empty] locale
+                        if (locale.size() > 0) {
+                            fprintf(stderr, "aapt: warning: string '%s' in %s marked untranslatable but exists"
+                                    " in locale '%s'\n", String8(name).string(),
+                                    bundle->getResourceSourceDirs()[0],
+                                    locale.string());
+                            // hasErrors = localHasErrors = true;
+                        } else {
+                            // Intentionally empty block:
+                            //
+                            // Don't add untranslatable strings to the localization table; that
+                            // way if we later see localizations of them, they'll be flagged as
+                            // having no default translation.
+                        }
+                    } else {
+                        outTable->addLocalization(name, locale);
+                    }
+
+                    if (formatted == false16) {
+                        curIsFormatted = false;
+                    }
+                }
+
+                curTag = &string16;
+                curType = string16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
+                curIsStyled = true;
+                curIsPseudolocalizable = true;
+            } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) {
+                curTag = &drawable16;
+                curType = drawable16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
+            } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) {
+                curTag = &color16;
+                curType = color16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
+            } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) {
+                curTag = &bool16;
+                curType = bool16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN;
+            } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) {
+                curTag = &integer16;
+                curType = integer16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
+            } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) {
+                curTag = &dimen16;
+                curType = dimen16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION;
+            } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) {
+                curTag = &fraction16;
+                curType = fraction16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION;
+            } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) {
+                curTag = &bag16;
+                curIsBag = true;
+                ssize_t attri = block.indexOfAttribute(NULL, "type");
+                if (attri >= 0) {
+                    curType = String16(block.getAttributeStringValue(attri, &len));
+                } else {
+                    SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                            "A 'type' attribute is required for <bag>\n");
+                    hasErrors = localHasErrors = true;
+                }
+            } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) {
+                curTag = &style16;
+                curType = style16;
+                curIsBag = true;
+            } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) {
+                curTag = &plurals16;
+                curType = plurals16;
+                curIsBag = true;
+            } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) {
+                curTag = &array16;
+                curType = array16;
+                curIsBag = true;
+                curIsBagReplaceOnOverwrite = true;
+                ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
+                if (formatIdx >= 0) {
+                    String16 formatStr = String16(block.getAttributeStringValue(
+                            formatIdx, &len));
+                    curFormat = parse_flags(formatStr.string(), formatStr.size(),
+                                            gFormatFlags);
+                    if (curFormat == 0) {
+                        SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                "Tag <array> 'format' attribute value \"%s\" not valid\n",
+                                String8(formatStr).string());
+                        hasErrors = localHasErrors = true;
+                    }
+                }
+            } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) {
+                // Check whether these strings need valid formats.
+                // (simplified form of what string16 does above)
+                size_t n = block.getAttributeCount();
+                for (size_t i = 0; i < n; i++) {
+                    size_t length;
+                    const uint16_t* attr = block.getAttributeName(i, &length);
+                    if (strcmp16(attr, translatable16.string()) == 0
+                            || strcmp16(attr, formatted16.string()) == 0) {
+                        const uint16_t* value = block.getAttributeStringValue(i, &length);
+                        if (strcmp16(value, false16.string()) == 0) {
+                            curIsFormatted = false;
+                            break;
+                        }
+                    }
+                }
+
+                curTag = &string_array16;
+                curType = array16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
+                curIsBag = true;
+                curIsBagReplaceOnOverwrite = true;
+                curIsPseudolocalizable = true;
+            } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) {
+                curTag = &integer_array16;
+                curType = array16;
+                curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
+                curIsBag = true;
+                curIsBagReplaceOnOverwrite = true;
+            } else {
+                SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                        "Found tag %s where item is expected\n",
+                        String8(block.getElementName(&len)).string());
+                return UNKNOWN_ERROR;
+            }
+
+            String16 ident;
+            ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+            if (identIdx >= 0) {
+                ident = String16(block.getAttributeStringValue(identIdx, &len));
+            } else {
+                SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                        "A 'name' attribute is required for <%s>\n",
+                        String8(*curTag).string());
+                hasErrors = localHasErrors = true;
+            }
+
+            String16 product;
+            identIdx = block.indexOfAttribute(NULL, "product");
+            if (identIdx >= 0) {
+                product = String16(block.getAttributeStringValue(identIdx, &len));
+            }
+
+            String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
+            
+            if (curIsBag) {
+                // Figure out the parent of this bag...
+                String16 parentIdent;
+                ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent");
+                if (parentIdentIdx >= 0) {
+                    parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len));
+                } else {
+                    ssize_t sep = ident.findLast('.');
+                    if (sep >= 0) {
+                        parentIdent.setTo(ident, sep);
+                    }
+                }
+
+                if (!localHasErrors) {
+                    err = outTable->startBag(SourcePos(in->getPrintableSource(),
+                            block.getLineNumber()), myPackage, curType, ident,
+                            parentIdent, &curParams,
+                            overwrite, curIsBagReplaceOnOverwrite);
+                    if (err != NO_ERROR) {
+                        hasErrors = localHasErrors = true;
+                    }
+                }
+                
+                ssize_t elmIndex = 0;
+                char elmIndexStr[14];
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+                        && code != ResXMLTree::BAD_DOCUMENT) {
+
+                    if (code == ResXMLTree::START_TAG) {
+                        if (strcmp16(block.getElementName(&len), item16.string()) != 0) {
+                            SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                    "Tag <%s> can not appear inside <%s>, only <item>\n",
+                                    String8(block.getElementName(&len)).string(),
+                                    String8(*curTag).string());
+                            return UNKNOWN_ERROR;
+                        }
+
+                        String16 itemIdent;
+                        if (curType == array16) {
+                            sprintf(elmIndexStr, "^index_%d", (int)elmIndex++);
+                            itemIdent = String16(elmIndexStr);
+                        } else if (curType == plurals16) {
+                            ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity");
+                            if (itemIdentIdx >= 0) {
+                                String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len));
+                                if (quantity16 == other16) {
+                                    itemIdent = quantityOther16;
+                                }
+                                else if (quantity16 == zero16) {
+                                    itemIdent = quantityZero16;
+                                }
+                                else if (quantity16 == one16) {
+                                    itemIdent = quantityOne16;
+                                }
+                                else if (quantity16 == two16) {
+                                    itemIdent = quantityTwo16;
+                                }
+                                else if (quantity16 == few16) {
+                                    itemIdent = quantityFew16;
+                                }
+                                else if (quantity16 == many16) {
+                                    itemIdent = quantityMany16;
+                                }
+                                else {
+                                    SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                            "Illegal 'quantity' attribute is <item> inside <plurals>\n");
+                                    hasErrors = localHasErrors = true;
+                                }
+                            } else {
+                                SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                        "A 'quantity' attribute is required for <item> inside <plurals>\n");
+                                hasErrors = localHasErrors = true;
+                            }
+                        } else {
+                            ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
+                            if (itemIdentIdx >= 0) {
+                                itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
+                            } else {
+                                SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                        "A 'name' attribute is required for <item>\n");
+                                hasErrors = localHasErrors = true;
+                            }
+                        }
+
+                        ResXMLParser::ResXMLPosition parserPosition;
+                        block.getPosition(&parserPosition);
+
+                        err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
+                                ident, parentIdent, itemIdent, curFormat, curIsFormatted,
+                                product, false, overwrite, outTable);
+                        if (err == NO_ERROR) {
+                            if (curIsPseudolocalizable && localeIsDefined(curParams)
+                                    && bundle->getPseudolocalize()) {
+                                // pseudolocalize here
+#if 1
+                                block.setPosition(parserPosition);
+                                err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
+                                        curType, ident, parentIdent, itemIdent, curFormat,
+                                        curIsFormatted, product, true, overwrite, outTable);
+#endif
+                            }
+                        } 
+                        if (err != NO_ERROR) {
+                            hasErrors = localHasErrors = true;
+                        }
+                    } else if (code == ResXMLTree::END_TAG) {
+                        if (strcmp16(block.getElementName(&len), curTag->string()) != 0) {
+                            SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                                    "Found tag </%s> where </%s> is expected\n",
+                                    String8(block.getElementName(&len)).string(),
+                                    String8(*curTag).string());
+                            return UNKNOWN_ERROR;
+                        }
+                        break;
+                    }
+                }
+            } else {
+                ResXMLParser::ResXMLPosition parserPosition;
+                block.getPosition(&parserPosition);
+
+                err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
+                        *curTag, curIsStyled, curFormat, curIsFormatted,
+                        product, false, overwrite, outTable);
+
+                if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
+                    hasErrors = localHasErrors = true;
+                }
+                else if (err == NO_ERROR) {
+                    if (curIsPseudolocalizable && localeIsDefined(curParams)
+                            && bundle->getPseudolocalize()) {
+                        // pseudolocalize here
+                        block.setPosition(parserPosition);
+                        err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
+                                ident, *curTag, curIsStyled, curFormat,
+                                curIsFormatted, product,
+                                true, overwrite, outTable);
+                        if (err != NO_ERROR) {
+                            hasErrors = localHasErrors = true;
+                        }
+                    }
+                }
+            }
+
+#if 0
+            if (comment.size() > 0) {
+                printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(),
+                       String8(curType).string(), String8(ident).string(),
+                       String8(comment).string());
+            }
+#endif
+            if (!localHasErrors) {
+                outTable->appendComment(myPackage, curType, ident, comment, false);
+            }
+        }
+        else if (code == ResXMLTree::END_TAG) {
+            if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
+                SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                        "Unexpected end tag %s\n", String8(block.getElementName(&len)).string());
+                return UNKNOWN_ERROR;
+            }
+        }
+        else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) {
+        }
+        else if (code == ResXMLTree::TEXT) {
+            if (isWhitespace(block.getText(&len))) {
+                continue;
+            }
+            SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+                    "Found text \"%s\" where item tag is expected\n",
+                    String8(block.getText(&len)).string());
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage)
+    : mAssetsPackage(assetsPackage), mNextPackageId(1), mHaveAppPackage(false),
+      mIsAppPackage(!bundle->getExtending()),
+      mNumLocal(0),
+      mBundle(bundle)
+{
+}
+
+status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+    status_t err = assets->buildIncludedResources(bundle);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    // For future reference to included resources.
+    mAssets = assets;
+
+    const ResTable& incl = assets->getIncludedResources();
+
+    // Retrieve all the packages.
+    const size_t N = incl.getBasePackageCount();
+    for (size_t phase=0; phase<2; phase++) {
+        for (size_t i=0; i<N; i++) {
+            String16 name(incl.getBasePackageName(i));
+            uint32_t id = incl.getBasePackageId(i);
+            // First time through: only add base packages (id
+            // is not 0); second time through add the other
+            // packages.
+            if (phase != 0) {
+                if (id != 0) {
+                    // Skip base packages -- already one.
+                    id = 0;
+                } else {
+                    // Assign a dynamic id.
+                    id = mNextPackageId;
+                }
+            } else if (id != 0) {
+                if (id == 127) {
+                    if (mHaveAppPackage) {
+                        fprintf(stderr, "Included resources have two application packages!\n");
+                        return UNKNOWN_ERROR;
+                    }
+                    mHaveAppPackage = true;
+                }
+                if (mNextPackageId > id) {
+                    fprintf(stderr, "Included base package ID %d already in use!\n", id);
+                    return UNKNOWN_ERROR;
+                }
+            }
+            if (id != 0) {
+                NOISY(printf("Including package %s with ID=%d\n",
+                             String8(name).string(), id));
+                sp<Package> p = new Package(name, id);
+                mPackages.add(name, p);
+                mOrderedPackages.add(p);
+
+                if (id >= mNextPackageId) {
+                    mNextPackageId = id+1;
+                }
+            }
+        }
+    }
+
+    // Every resource table always has one first entry, the bag attributes.
+    const SourcePos unknown(String8("????"), 0);
+    sp<Type> attr = getType(mAssetsPackage, String16("attr"), unknown);
+
+    return NO_ERROR;
+}
+
+status_t ResourceTable::addPublic(const SourcePos& sourcePos,
+                                  const String16& package,
+                                  const String16& type,
+                                  const String16& name,
+                                  const uint32_t ident)
+{
+    uint32_t rid = mAssets->getIncludedResources()
+        .identifierForName(name.string(), name.size(),
+                           type.string(), type.size(),
+                           package.string(), package.size());
+    if (rid != 0) {
+        sourcePos.error("Error declaring public resource %s/%s for included package %s\n",
+                String8(type).string(), String8(name).string(),
+                String8(package).string());
+        return UNKNOWN_ERROR;
+    }
+
+    sp<Type> t = getType(package, type, sourcePos);
+    if (t == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    return t->addPublic(sourcePos, name, ident);
+}
+
+status_t ResourceTable::addEntry(const SourcePos& sourcePos,
+                                 const String16& package,
+                                 const String16& type,
+                                 const String16& name,
+                                 const String16& value,
+                                 const Vector<StringPool::entry_style_span>* style,
+                                 const ResTable_config* params,
+                                 const bool doSetIndex,
+                                 const int32_t format,
+                                 const bool overwrite)
+{
+    // Check for adding entries in other packages...  for now we do
+    // nothing.  We need to do the right thing here to support skinning.
+    uint32_t rid = mAssets->getIncludedResources()
+        .identifierForName(name.string(), name.size(),
+                           type.string(), type.size(),
+                           package.string(), package.size());
+    if (rid != 0) {
+        return NO_ERROR;
+    }
+    
+#if 0
+    if (name == String16("left")) {
+        printf("Adding entry left: file=%s, line=%d, type=%s, value=%s\n",
+               sourcePos.file.string(), sourcePos.line, String8(type).string(),
+               String8(value).string());
+    }
+#endif
+
+    sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
+                           params, doSetIndex);
+    if (e == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    status_t err = e->setItem(sourcePos, value, style, format, overwrite);
+    if (err == NO_ERROR) {
+        mNumLocal++;
+    }
+    return err;
+}
+
+status_t ResourceTable::startBag(const SourcePos& sourcePos,
+                                 const String16& package,
+                                 const String16& type,
+                                 const String16& name,
+                                 const String16& bagParent,
+                                 const ResTable_config* params,
+                                 bool overlay,
+                                 bool replace, bool isId)
+{
+    status_t result = NO_ERROR;
+
+    // Check for adding entries in other packages...  for now we do
+    // nothing.  We need to do the right thing here to support skinning.
+    uint32_t rid = mAssets->getIncludedResources()
+    .identifierForName(name.string(), name.size(),
+                       type.string(), type.size(),
+                       package.string(), package.size());
+    if (rid != 0) {
+        return NO_ERROR;
+    }
+    
+#if 0
+    if (name == String16("left")) {
+        printf("Adding bag left: file=%s, line=%d, type=%s\n",
+               sourcePos.file.striing(), sourcePos.line, String8(type).string());
+    }
+#endif
+    if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) {
+        bool canAdd = false;
+        sp<Package> p = mPackages.valueFor(package);
+        if (p != NULL) {
+            sp<Type> t = p->getTypes().valueFor(type);
+            if (t != NULL) {
+                if (t->getCanAddEntries().indexOf(name) >= 0) {
+                    canAdd = true;
+                }
+            }
+        }
+        if (!canAdd) {
+            sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n",
+                            String8(name).string());
+            return UNKNOWN_ERROR;
+        }
+    }
+    sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params);
+    if (e == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    
+    // If a parent is explicitly specified, set it.
+    if (bagParent.size() > 0) {
+        e->setParent(bagParent);
+    }
+
+    if ((result = e->makeItABag(sourcePos)) != NO_ERROR) {
+        return result;
+    }
+
+    if (overlay && replace) { 
+        return e->emptyBag(sourcePos);
+    }
+    return result;
+}
+
+status_t ResourceTable::addBag(const SourcePos& sourcePos,
+                               const String16& package,
+                               const String16& type,
+                               const String16& name,
+                               const String16& bagParent,
+                               const String16& bagKey,
+                               const String16& value,
+                               const Vector<StringPool::entry_style_span>* style,
+                               const ResTable_config* params,
+                               bool replace, bool isId, const int32_t format)
+{
+    // Check for adding entries in other packages...  for now we do
+    // nothing.  We need to do the right thing here to support skinning.
+    uint32_t rid = mAssets->getIncludedResources()
+        .identifierForName(name.string(), name.size(),
+                           type.string(), type.size(),
+                           package.string(), package.size());
+    if (rid != 0) {
+        return NO_ERROR;
+    }
+
+#if 0
+    if (name == String16("left")) {
+        printf("Adding bag left: file=%s, line=%d, type=%s\n",
+               sourcePos.file.striing(), sourcePos.line, String8(type).string());
+    }
+#endif
+    sp<Entry> e = getEntry(package, type, name, sourcePos, replace, params);
+    if (e == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    // If a parent is explicitly specified, set it.
+    if (bagParent.size() > 0) {
+        e->setParent(bagParent);
+    }
+
+    const bool first = e->getBag().indexOfKey(bagKey) < 0;
+    status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format);
+    if (err == NO_ERROR && first) {
+        mNumLocal++;
+    }
+    return err;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& package,
+                                  const String16& type,
+                                  const String16& name) const
+{
+    // First look for this in the included resources...
+    uint32_t rid = mAssets->getIncludedResources()
+        .identifierForName(name.string(), name.size(),
+                           type.string(), type.size(),
+                           package.string(), package.size());
+    if (rid != 0) {
+        return true;
+    }
+
+    sp<Package> p = mPackages.valueFor(package);
+    if (p != NULL) {
+        sp<Type> t = p->getTypes().valueFor(type);
+        if (t != NULL) {
+            sp<ConfigList> c =  t->getConfigs().valueFor(name);
+            if (c != NULL) return true;
+        }
+    }
+
+    return false;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& package,
+                                  const String16& type,
+                                  const String16& name,
+                                  const ResTable_config& config) const
+{
+    // First look for this in the included resources...
+    uint32_t rid = mAssets->getIncludedResources()
+        .identifierForName(name.string(), name.size(),
+                           type.string(), type.size(),
+                           package.string(), package.size());
+    if (rid != 0) {
+        return true;
+    }
+
+    sp<Package> p = mPackages.valueFor(package);
+    if (p != NULL) {
+        sp<Type> t = p->getTypes().valueFor(type);
+        if (t != NULL) {
+            sp<ConfigList> c =  t->getConfigs().valueFor(name);
+            if (c != NULL) {
+                sp<Entry> e = c->getEntries().valueFor(config);
+                if (e != NULL) {
+                    return true;
+                }
+            }
+        }
+    }
+
+    return false;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& ref,
+                                  const String16* defType,
+                                  const String16* defPackage)
+{
+    String16 package, type, name;
+    if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name,
+                defType, defPackage ? defPackage:&mAssetsPackage, NULL)) {
+        return false;
+    }
+    return hasBagOrEntry(package, type, name);
+}
+
+bool ResourceTable::appendComment(const String16& package,
+                                  const String16& type,
+                                  const String16& name,
+                                  const String16& comment,
+                                  bool onlyIfEmpty)
+{
+    if (comment.size() <= 0) {
+        return true;
+    }
+
+    sp<Package> p = mPackages.valueFor(package);
+    if (p != NULL) {
+        sp<Type> t = p->getTypes().valueFor(type);
+        if (t != NULL) {
+            sp<ConfigList> c =  t->getConfigs().valueFor(name);
+            if (c != NULL) {
+                c->appendComment(comment, onlyIfEmpty);
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool ResourceTable::appendTypeComment(const String16& package,
+                                      const String16& type,
+                                      const String16& name,
+                                      const String16& comment)
+{
+    if (comment.size() <= 0) {
+        return true;
+    }
+    
+    sp<Package> p = mPackages.valueFor(package);
+    if (p != NULL) {
+        sp<Type> t = p->getTypes().valueFor(type);
+        if (t != NULL) {
+            sp<ConfigList> c =  t->getConfigs().valueFor(name);
+            if (c != NULL) {
+                c->appendTypeComment(comment);
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void ResourceTable::canAddEntry(const SourcePos& pos,
+        const String16& package, const String16& type, const String16& name)
+{
+    sp<Type> t = getType(package, type, pos);
+    if (t != NULL) {
+        t->canAddEntry(name);
+    }
+}
+
+size_t ResourceTable::size() const {
+    return mPackages.size();
+}
+
+size_t ResourceTable::numLocalResources() const {
+    return mNumLocal;
+}
+
+bool ResourceTable::hasResources() const {
+    return mNumLocal > 0;
+}
+
+sp<AaptFile> ResourceTable::flatten(Bundle* bundle)
+{
+    sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
+    status_t err = flatten(bundle, data);
+    return err == NO_ERROR ? data : NULL;
+}
+
+inline uint32_t ResourceTable::getResId(const sp<Package>& p,
+                                        const sp<Type>& t,
+                                        uint32_t nameId)
+{
+    return makeResId(p->getAssignedId(), t->getIndex(), nameId);
+}
+
+uint32_t ResourceTable::getResId(const String16& package,
+                                 const String16& type,
+                                 const String16& name,
+                                 bool onlyPublic) const
+{
+    uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic);
+    if (id != 0) return id;     // cache hit
+
+    sp<Package> p = mPackages.valueFor(package);
+    if (p == NULL) return 0;
+
+    // First look for this in the included resources...
+    uint32_t specFlags = 0;
+    uint32_t rid = mAssets->getIncludedResources()
+        .identifierForName(name.string(), name.size(),
+                           type.string(), type.size(),
+                           package.string(), package.size(),
+                           &specFlags);
+    if (rid != 0) {
+        if (onlyPublic) {
+            if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+                return 0;
+            }
+        }
+        
+        if (Res_INTERNALID(rid)) {
+            return ResourceIdCache::store(package, type, name, onlyPublic, rid);
+        }
+        return ResourceIdCache::store(package, type, name, onlyPublic,
+                Res_MAKEID(p->getAssignedId()-1, Res_GETTYPE(rid), Res_GETENTRY(rid)));
+    }
+
+    sp<Type> t = p->getTypes().valueFor(type);
+    if (t == NULL) return 0;
+    sp<ConfigList> c =  t->getConfigs().valueFor(name);
+    if (c == NULL) return 0;
+    int32_t ei = c->getEntryIndex();
+    if (ei < 0) return 0;
+
+    return ResourceIdCache::store(package, type, name, onlyPublic,
+            getResId(p, t, ei));
+}
+
+uint32_t ResourceTable::getResId(const String16& ref,
+                                 const String16* defType,
+                                 const String16* defPackage,
+                                 const char** outErrorMsg,
+                                 bool onlyPublic) const
+{
+    String16 package, type, name;
+    bool refOnlyPublic = true;
+    if (!ResTable::expandResourceRef(
+        ref.string(), ref.size(), &package, &type, &name,
+        defType, defPackage ? defPackage:&mAssetsPackage,
+        outErrorMsg, &refOnlyPublic)) {
+        NOISY(printf("Expanding resource: ref=%s\n",
+                     String8(ref).string()));
+        NOISY(printf("Expanding resource: defType=%s\n",
+                     defType ? String8(*defType).string() : "NULL"));
+        NOISY(printf("Expanding resource: defPackage=%s\n",
+                     defPackage ? String8(*defPackage).string() : "NULL"));
+        NOISY(printf("Expanding resource: ref=%s\n", String8(ref).string()));
+        NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n",
+                     String8(package).string(), String8(type).string(),
+                     String8(name).string()));
+        return 0;
+    }
+    uint32_t res = getResId(package, type, name, onlyPublic && refOnlyPublic);
+    NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n",
+                 String8(package).string(), String8(type).string(),
+                 String8(name).string(), res));
+    if (res == 0) {
+        if (outErrorMsg)
+            *outErrorMsg = "No resource found that matches the given name";
+    }
+    return res;
+}
+
+bool ResourceTable::isValidResourceName(const String16& s)
+{
+    const char16_t* p = s.string();
+    bool first = true;
+    while (*p) {
+        if ((*p >= 'a' && *p <= 'z')
+            || (*p >= 'A' && *p <= 'Z')
+            || *p == '_'
+            || (!first && *p >= '0' && *p <= '9')) {
+            first = false;
+            p++;
+            continue;
+        }
+        return false;
+    }
+    return true;
+}
+
+bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool,
+                                  const String16& str,
+                                  bool preserveSpaces, bool coerceType,
+                                  uint32_t attrID,
+                                  const Vector<StringPool::entry_style_span>* style,
+                                  String16* outStr, void* accessorCookie,
+                                  uint32_t attrType, const String8* configTypeName,
+                                  const ConfigDescription* config)
+{
+    String16 finalStr;
+
+    bool res = true;
+    if (style == NULL || style->size() == 0) {
+        // Text is not styled so it can be any type...  let's figure it out.
+        res = mAssets->getIncludedResources()
+            .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces,
+                            coerceType, attrID, NULL, &mAssetsPackage, this,
+                           accessorCookie, attrType);
+    } else {
+        // Styled text can only be a string, and while collecting the style
+        // information we have already processed that string!
+        outValue->size = sizeof(Res_value);
+        outValue->res0 = 0;
+        outValue->dataType = outValue->TYPE_STRING;
+        outValue->data = 0;
+        finalStr = str;
+    }
+
+    if (!res) {
+        return false;
+    }
+
+    if (outValue->dataType == outValue->TYPE_STRING) {
+        // Should do better merging styles.
+        if (pool) {
+            String8 configStr;
+            if (config != NULL) {
+                configStr = config->toString();
+            } else {
+                configStr = "(null)";
+            }
+            NOISY(printf("Adding to pool string style #%d config %s: %s\n",
+                    style != NULL ? style->size() : 0,
+                    configStr.string(), String8(finalStr).string()));
+            if (style != NULL && style->size() > 0) {
+                outValue->data = pool->add(finalStr, *style, configTypeName, config);
+            } else {
+                outValue->data = pool->add(finalStr, true, configTypeName, config);
+            }
+        } else {
+            // Caller will fill this in later.
+            outValue->data = 0;
+        }
+    
+        if (outStr) {
+            *outStr = finalStr;
+        }
+
+    }
+
+    return true;
+}
+
+uint32_t ResourceTable::getCustomResource(
+    const String16& package, const String16& type, const String16& name) const
+{
+    //printf("getCustomResource: %s %s %s\n", String8(package).string(),
+    //       String8(type).string(), String8(name).string());
+    sp<Package> p = mPackages.valueFor(package);
+    if (p == NULL) return 0;
+    sp<Type> t = p->getTypes().valueFor(type);
+    if (t == NULL) return 0;
+    sp<ConfigList> c =  t->getConfigs().valueFor(name);
+    if (c == NULL) return 0;
+    int32_t ei = c->getEntryIndex();
+    if (ei < 0) return 0;
+    return getResId(p, t, ei);
+}
+
+uint32_t ResourceTable::getCustomResourceWithCreation(
+        const String16& package, const String16& type, const String16& name,
+        const bool createIfNotFound)
+{
+    uint32_t resId = getCustomResource(package, type, name);
+    if (resId != 0 || !createIfNotFound) {
+        return resId;
+    }
+    String16 value("false");
+
+    status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true);
+    if (status == NO_ERROR) {
+        resId = getResId(package, type, name);
+        return resId;
+    }
+    return 0;
+}
+
+uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const
+{
+    return origPackage;
+}
+
+bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType)
+{
+    //printf("getAttributeType #%08x\n", attrID);
+    Res_value value;
+    if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) {
+        //printf("getAttributeType #%08x (%s): #%08x\n", attrID,
+        //       String8(getEntry(attrID)->getName()).string(), value.data);
+        *outType = value.data;
+        return true;
+    }
+    return false;
+}
+
+bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin)
+{
+    //printf("getAttributeMin #%08x\n", attrID);
+    Res_value value;
+    if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) {
+        *outMin = value.data;
+        return true;
+    }
+    return false;
+}
+
+bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax)
+{
+    //printf("getAttributeMax #%08x\n", attrID);
+    Res_value value;
+    if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) {
+        *outMax = value.data;
+        return true;
+    }
+    return false;
+}
+
+uint32_t ResourceTable::getAttributeL10N(uint32_t attrID)
+{
+    //printf("getAttributeL10N #%08x\n", attrID);
+    Res_value value;
+    if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) {
+        return value.data;
+    }
+    return ResTable_map::L10N_NOT_REQUIRED;
+}
+
+bool ResourceTable::getLocalizationSetting()
+{
+    return mBundle->getRequireLocalization();
+}
+
+void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...)
+{
+    if (accessorCookie != NULL && fmt != NULL) {
+        AccessorCookie* ac = (AccessorCookie*)accessorCookie;
+        int retval=0;
+        char buf[1024];
+        va_list ap;
+        va_start(ap, fmt);
+        retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+        va_end(ap);
+        ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n",
+                            buf, ac->attr.string(), ac->value.string());
+    }
+}
+
+bool ResourceTable::getAttributeKeys(
+    uint32_t attrID, Vector<String16>* outKeys)
+{
+    sp<const Entry> e = getEntry(attrID);
+    if (e != NULL) {
+        const size_t N = e->getBag().size();
+        for (size_t i=0; i<N; i++) {
+            const String16& key = e->getBag().keyAt(i);
+            if (key.size() > 0 && key.string()[0] != '^') {
+                outKeys->add(key);
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+bool ResourceTable::getAttributeEnum(
+    uint32_t attrID, const char16_t* name, size_t nameLen,
+    Res_value* outValue)
+{
+    //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string());
+    String16 nameStr(name, nameLen);
+    sp<const Entry> e = getEntry(attrID);
+    if (e != NULL) {
+        const size_t N = e->getBag().size();
+        for (size_t i=0; i<N; i++) {
+            //printf("Comparing %s to %s\n", String8(name, nameLen).string(),
+            //       String8(e->getBag().keyAt(i)).string());
+            if (e->getBag().keyAt(i) == nameStr) {
+                return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue);
+            }
+        }
+    }
+    return false;
+}
+
+bool ResourceTable::getAttributeFlags(
+    uint32_t attrID, const char16_t* name, size_t nameLen,
+    Res_value* outValue)
+{
+    outValue->dataType = Res_value::TYPE_INT_HEX;
+    outValue->data = 0;
+
+    //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string());
+    String16 nameStr(name, nameLen);
+    sp<const Entry> e = getEntry(attrID);
+    if (e != NULL) {
+        const size_t N = e->getBag().size();
+
+        const char16_t* end = name + nameLen;
+        const char16_t* pos = name;
+        while (pos < end) {
+            const char16_t* start = pos;
+            while (pos < end && *pos != '|') {
+                pos++;
+            }
+
+            String16 nameStr(start, pos-start);
+            size_t i;
+            for (i=0; i<N; i++) {
+                //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(),
+                //       String8(e->getBag().keyAt(i)).string());
+                if (e->getBag().keyAt(i) == nameStr) {
+                    Res_value val;
+                    bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val);
+                    if (!got) {
+                        return false;
+                    }
+                    //printf("Got value: 0x%08x\n", val.data);
+                    outValue->data |= val.data;
+                    break;
+                }
+            }
+
+            if (i >= N) {
+                // Didn't find this flag identifier.
+                return false;
+            }
+            pos++;
+        }
+
+        return true;
+    }
+    return false;
+}
+
+status_t ResourceTable::assignResourceIds()
+{
+    const size_t N = mOrderedPackages.size();
+    size_t pi;
+    status_t firstError = NO_ERROR;
+
+    // First generate all bag attributes and assign indices.
+    for (pi=0; pi<N; pi++) {
+        sp<Package> p = mOrderedPackages.itemAt(pi);
+        if (p == NULL || p->getTypes().size() == 0) {
+            // Empty, skip!
+            continue;
+        }
+
+        status_t err = p->applyPublicTypeOrder();
+        if (err != NO_ERROR && firstError == NO_ERROR) {
+            firstError = err;
+        }
+
+        // Generate attributes...
+        const size_t N = p->getOrderedTypes().size();
+        size_t ti;
+        for (ti=0; ti<N; ti++) {
+            sp<Type> t = p->getOrderedTypes().itemAt(ti);
+            if (t == NULL) {
+                continue;
+            }
+            const size_t N = t->getOrderedConfigs().size();
+            for (size_t ci=0; ci<N; ci++) {
+                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+                if (c == NULL) {
+                    continue;
+                }
+                const size_t N = c->getEntries().size();
+                for (size_t ei=0; ei<N; ei++) {
+                    sp<Entry> e = c->getEntries().valueAt(ei);
+                    if (e == NULL) {
+                        continue;
+                    }
+                    status_t err = e->generateAttributes(this, p->getName());
+                    if (err != NO_ERROR && firstError == NO_ERROR) {
+                        firstError = err;
+                    }
+                }
+            }
+        }
+
+        const SourcePos unknown(String8("????"), 0);
+        sp<Type> attr = p->getType(String16("attr"), unknown);
+
+        // Assign indices...
+        for (ti=0; ti<N; ti++) {
+            sp<Type> t = p->getOrderedTypes().itemAt(ti);
+            if (t == NULL) {
+                continue;
+            }
+            err = t->applyPublicEntryOrder();
+            if (err != NO_ERROR && firstError == NO_ERROR) {
+                firstError = err;
+            }
+
+            const size_t N = t->getOrderedConfigs().size();
+            t->setIndex(ti+1);
+
+            LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
+                                "First type is not attr!");
+
+            for (size_t ei=0; ei<N; ei++) {
+                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
+                if (c == NULL) {
+                    continue;
+                }
+                c->setEntryIndex(ei);
+            }
+        }
+
+        // Assign resource IDs to keys in bags...
+        for (ti=0; ti<N; ti++) {
+            sp<Type> t = p->getOrderedTypes().itemAt(ti);
+            if (t == NULL) {
+                continue;
+            }
+            const size_t N = t->getOrderedConfigs().size();
+            for (size_t ci=0; ci<N; ci++) {
+                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+                //printf("Ordered config #%d: %p\n", ci, c.get());
+                const size_t N = c->getEntries().size();
+                for (size_t ei=0; ei<N; ei++) {
+                    sp<Entry> e = c->getEntries().valueAt(ei);
+                    if (e == NULL) {
+                        continue;
+                    }
+                    status_t err = e->assignResourceIds(this, p->getName());
+                    if (err != NO_ERROR && firstError == NO_ERROR) {
+                        firstError = err;
+                    }
+                }
+            }
+        }
+    }
+    return firstError;
+}
+
+status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) {
+    const size_t N = mOrderedPackages.size();
+    size_t pi;
+
+    for (pi=0; pi<N; pi++) {
+        sp<Package> p = mOrderedPackages.itemAt(pi);
+        if (p->getTypes().size() == 0) {
+            // Empty, skip!
+            continue;
+        }
+
+        const size_t N = p->getOrderedTypes().size();
+        size_t ti;
+
+        for (ti=0; ti<N; ti++) {
+            sp<Type> t = p->getOrderedTypes().itemAt(ti);
+            if (t == NULL) {
+                continue;
+            }
+            const size_t N = t->getOrderedConfigs().size();
+            sp<AaptSymbols> typeSymbols;
+            typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
+            for (size_t ci=0; ci<N; ci++) {
+                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+                if (c == NULL) {
+                    continue;
+                }
+                uint32_t rid = getResId(p, t, ci);
+                if (rid == 0) {
+                    return UNKNOWN_ERROR;
+                }
+                if (Res_GETPACKAGE(rid) == (size_t)(p->getAssignedId()-1)) {
+                    typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
+                    
+                    String16 comment(c->getComment());
+                    typeSymbols->appendComment(String8(c->getName()), comment, c->getPos());
+                    //printf("Type symbol %s comment: %s\n", String8(e->getName()).string(),
+                    //     String8(comment).string());
+                    comment = c->getTypeComment();
+                    typeSymbols->appendTypeComment(String8(c->getName()), comment);
+                } else {
+#if 0
+                    printf("**** NO MATCH: 0x%08x vs 0x%08x\n",
+                           Res_GETPACKAGE(rid), p->getAssignedId());
+#endif
+                }
+            }
+        }
+    }
+    return NO_ERROR;
+}
+
+
+void
+ResourceTable::addLocalization(const String16& name, const String8& locale)
+{
+    mLocalizations[name].insert(locale);
+}
+
+
+/*!
+ * Flag various sorts of localization problems.  '+' indicates checks already implemented;
+ * '-' indicates checks that will be implemented in the future.
+ *
+ * + A localized string for which no default-locale version exists => warning
+ * + A string for which no version in an explicitly-requested locale exists => warning
+ * + A localized translation of an translateable="false" string => warning
+ * - A localized string not provided in every locale used by the table
+ */
+status_t
+ResourceTable::validateLocalizations(void)
+{
+    status_t err = NO_ERROR;
+    const String8 defaultLocale;
+
+    // For all strings...
+    for (map<String16, set<String8> >::iterator nameIter = mLocalizations.begin();
+         nameIter != mLocalizations.end();
+         nameIter++) {
+        const set<String8>& configSet = nameIter->second;   // naming convenience
+
+        // Look for strings with no default localization
+        if (configSet.count(defaultLocale) == 0) {
+            fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:",
+                    String8(nameIter->first).string(), mBundle->getResourceSourceDirs()[0]);
+            for (set<String8>::const_iterator locales = configSet.begin();
+                 locales != configSet.end();
+                 locales++) {
+                fprintf(stdout, " %s", (*locales).string());
+            }
+            fprintf(stdout, "\n");
+            // !!! TODO: throw an error here in some circumstances
+        }
+
+        // Check that all requested localizations are present for this string
+        if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) {
+            const char* allConfigs = mBundle->getConfigurations();
+            const char* start = allConfigs;
+            const char* comma;
+            
+            do {
+                String8 config;
+                comma = strchr(start, ',');
+                if (comma != NULL) {
+                    config.setTo(start, comma - start);
+                    start = comma + 1;
+                } else {
+                    config.setTo(start);
+                }
+
+                // don't bother with the pseudolocale "zz_ZZ"
+                if (config != "zz_ZZ") {
+                    if (configSet.find(config) == configSet.end()) {
+                        // okay, no specific localization found.  it's possible that we are
+                        // requiring a specific regional localization [e.g. de_DE] but there is an
+                        // available string in the generic language localization [e.g. de];
+                        // consider that string to have fulfilled the localization requirement.
+                        String8 region(config.string(), 2);
+                        if (configSet.find(region) == configSet.end()) {
+                            if (configSet.count(defaultLocale) == 0) {
+                                fprintf(stdout, "aapt: warning: "
+                                        "**** string '%s' has no default or required localization "
+                                        "for '%s' in %s\n",
+                                        String8(nameIter->first).string(),
+                                        config.string(),
+                                        mBundle->getResourceSourceDirs()[0]);
+                            }
+                        }
+                    }
+                }
+           } while (comma != NULL);
+        }
+    }
+
+    return err;
+}
+
+status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest)
+{
+    ResourceFilter filter;
+    status_t err = filter.parse(bundle->getConfigurations());
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    const ConfigDescription nullConfig;
+
+    const size_t N = mOrderedPackages.size();
+    size_t pi;
+
+    const static String16 mipmap16("mipmap");
+
+    bool useUTF8 = !bundle->getUTF16StringsOption();
+
+    // Iterate through all data, collecting all values (strings,
+    // references, etc).
+    StringPool valueStrings(useUTF8);
+    Vector<sp<Entry> > allEntries;
+    for (pi=0; pi<N; pi++) {
+        sp<Package> p = mOrderedPackages.itemAt(pi);
+        if (p->getTypes().size() == 0) {
+            // Empty, skip!
+            continue;
+        }
+
+        StringPool typeStrings(useUTF8);
+        StringPool keyStrings(useUTF8);
+
+        const size_t N = p->getOrderedTypes().size();
+        for (size_t ti=0; ti<N; ti++) {
+            sp<Type> t = p->getOrderedTypes().itemAt(ti);
+            if (t == NULL) {
+                typeStrings.add(String16("<empty>"), false);
+                continue;
+            }
+            const String16 typeName(t->getName());
+            typeStrings.add(typeName, false);
+
+            // This is a hack to tweak the sorting order of the final strings,
+            // to put stuff that is generally not language-specific first.
+            String8 configTypeName(typeName);
+            if (configTypeName == "drawable" || configTypeName == "layout"
+                    || configTypeName == "color" || configTypeName == "anim"
+                    || configTypeName == "interpolator" || configTypeName == "animator"
+                    || configTypeName == "xml" || configTypeName == "menu"
+                    || configTypeName == "mipmap" || configTypeName == "raw") {
+                configTypeName = "1complex";
+            } else {
+                configTypeName = "2value";
+            }
+
+            const bool filterable = (typeName != mipmap16);
+
+            const size_t N = t->getOrderedConfigs().size();
+            for (size_t ci=0; ci<N; ci++) {
+                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+                if (c == NULL) {
+                    continue;
+                }
+                const size_t N = c->getEntries().size();
+                for (size_t ei=0; ei<N; ei++) {
+                    ConfigDescription config = c->getEntries().keyAt(ei);
+                    if (filterable && !filter.match(config)) {
+                        continue;
+                    }
+                    sp<Entry> e = c->getEntries().valueAt(ei);
+                    if (e == NULL) {
+                        continue;
+                    }
+                    e->setNameIndex(keyStrings.add(e->getName(), true));
+
+                    // If this entry has no values for other configs,
+                    // and is the default config, then it is special.  Otherwise
+                    // we want to add it with the config info.
+                    ConfigDescription* valueConfig = NULL;
+                    if (N != 1 || config == nullConfig) {
+                        valueConfig = &config;
+                    }
+
+                    status_t err = e->prepareFlatten(&valueStrings, this,
+                            &configTypeName, &config);
+                    if (err != NO_ERROR) {
+                        return err;
+                    }
+                    allEntries.add(e);
+                }
+            }
+        }
+
+        p->setTypeStrings(typeStrings.createStringBlock());
+        p->setKeyStrings(keyStrings.createStringBlock());
+    }
+
+    if (bundle->getOutputAPKFile() != NULL) {
+        // Now we want to sort the value strings for better locality.  This will
+        // cause the positions of the strings to change, so we need to go back
+        // through out resource entries and update them accordingly.  Only need
+        // to do this if actually writing the output file.
+        valueStrings.sortByConfig();
+        for (pi=0; pi<allEntries.size(); pi++) {
+            allEntries[pi]->remapStringValue(&valueStrings);
+        }
+    }
+
+    ssize_t strAmt = 0;
+    
+    // Now build the array of package chunks.
+    Vector<sp<AaptFile> > flatPackages;
+    for (pi=0; pi<N; pi++) {
+        sp<Package> p = mOrderedPackages.itemAt(pi);
+        if (p->getTypes().size() == 0) {
+            // Empty, skip!
+            continue;
+        }
+
+        const size_t N = p->getTypeStrings().size();
+
+        const size_t baseSize = sizeof(ResTable_package);
+
+        // Start the package data.
+        sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
+        ResTable_package* header = (ResTable_package*)data->editData(baseSize);
+        if (header == NULL) {
+            fprintf(stderr, "ERROR: out of memory creating ResTable_package\n");
+            return NO_MEMORY;
+        }
+        memset(header, 0, sizeof(*header));
+        header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
+        header->header.headerSize = htods(sizeof(*header));
+        header->id = htodl(p->getAssignedId());
+        strcpy16_htod(header->name, p->getName().string());
+
+        // Write the string blocks.
+        const size_t typeStringsStart = data->getSize();
+        sp<AaptFile> strFile = p->getTypeStringsData();
+        ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
+        #if PRINT_STRING_METRICS
+        fprintf(stderr, "**** type strings: %d\n", amt);
+        #endif
+        strAmt += amt;
+        if (amt < 0) {
+            return amt;
+        }
+        const size_t keyStringsStart = data->getSize();
+        strFile = p->getKeyStringsData();
+        amt = data->writeData(strFile->getData(), strFile->getSize());
+        #if PRINT_STRING_METRICS
+        fprintf(stderr, "**** key strings: %d\n", amt);
+        #endif
+        strAmt += amt;
+        if (amt < 0) {
+            return amt;
+        }
+
+        // Build the type chunks inside of this package.
+        for (size_t ti=0; ti<N; ti++) {
+            // Retrieve them in the same order as the type string block.
+            size_t len;
+            String16 typeName(p->getTypeStrings().stringAt(ti, &len));
+            sp<Type> t = p->getTypes().valueFor(typeName);
+            LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"),
+                                "Type name %s not found",
+                                String8(typeName).string());
+
+            const bool filterable = (typeName != mipmap16);
+
+            const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
+
+            // Until a non-NO_ENTRY value has been written for a resource,
+            // that resource is invalid; validResources[i] represents
+            // the item at t->getOrderedConfigs().itemAt(i).
+            Vector<bool> validResources;
+            validResources.insertAt(false, 0, N);
+            
+            // First write the typeSpec chunk, containing information about
+            // each resource entry in this type.
+            {
+                const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
+                const size_t typeSpecStart = data->getSize();
+                ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
+                    (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart);
+                if (tsHeader == NULL) {
+                    fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n");
+                    return NO_MEMORY;
+                }
+                memset(tsHeader, 0, sizeof(*tsHeader));
+                tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
+                tsHeader->header.headerSize = htods(sizeof(*tsHeader));
+                tsHeader->header.size = htodl(typeSpecSize);
+                tsHeader->id = ti+1;
+                tsHeader->entryCount = htodl(N);
+                
+                uint32_t* typeSpecFlags = (uint32_t*)
+                    (((uint8_t*)data->editData())
+                        + typeSpecStart + sizeof(ResTable_typeSpec));
+                memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
+
+                for (size_t ei=0; ei<N; ei++) {
+                    sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+                    if (cl->getPublic()) {
+                        typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
+                    }
+                    const size_t CN = cl->getEntries().size();
+                    for (size_t ci=0; ci<CN; ci++) {
+                        if (filterable && !filter.match(cl->getEntries().keyAt(ci))) {
+                            continue;
+                        }
+                        for (size_t cj=ci+1; cj<CN; cj++) {
+                            if (filterable && !filter.match(cl->getEntries().keyAt(cj))) {
+                                continue;
+                            }
+                            typeSpecFlags[ei] |= htodl(
+                                cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
+                        }
+                    }
+                }
+            }
+            
+            // We need to write one type chunk for each configuration for
+            // which we have entries in this type.
+            const size_t NC = t->getUniqueConfigs().size();
+            
+            const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
+            
+            for (size_t ci=0; ci<NC; ci++) {
+                ConfigDescription config = t->getUniqueConfigs().itemAt(ci);
+
+                NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
+                     "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
+                     "sw%ddp w%ddp h%ddp dir:%d\n",
+                      ti+1,
+                      config.mcc, config.mnc,
+                      config.language[0] ? config.language[0] : '-',
+                      config.language[1] ? config.language[1] : '-',
+                      config.country[0] ? config.country[0] : '-',
+                      config.country[1] ? config.country[1] : '-',
+                      config.orientation,
+                      config.uiMode,
+                      config.touchscreen,
+                      config.density,
+                      config.keyboard,
+                      config.inputFlags,
+                      config.navigation,
+                      config.screenWidth,
+                      config.screenHeight,
+                      config.smallestScreenWidthDp,
+                      config.screenWidthDp,
+                      config.screenHeightDp,
+                      config.layoutDirection));
+                      
+                if (filterable && !filter.match(config)) {
+                    continue;
+                }
+                
+                const size_t typeStart = data->getSize();
+
+                ResTable_type* tHeader = (ResTable_type*)
+                    (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
+                if (tHeader == NULL) {
+                    fprintf(stderr, "ERROR: out of memory creating ResTable_type\n");
+                    return NO_MEMORY;
+                }
+
+                memset(tHeader, 0, sizeof(*tHeader));
+                tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
+                tHeader->header.headerSize = htods(sizeof(*tHeader));
+                tHeader->id = ti+1;
+                tHeader->entryCount = htodl(N);
+                tHeader->entriesStart = htodl(typeSize);
+                tHeader->config = config;
+                NOISY(printf("Writing type %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
+                     "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
+                     "sw%ddp w%ddp h%ddp dir:%d\n",
+                      ti+1,
+                      tHeader->config.mcc, tHeader->config.mnc,
+                      tHeader->config.language[0] ? tHeader->config.language[0] : '-',
+                      tHeader->config.language[1] ? tHeader->config.language[1] : '-',
+                      tHeader->config.country[0] ? tHeader->config.country[0] : '-',
+                      tHeader->config.country[1] ? tHeader->config.country[1] : '-',
+                      tHeader->config.orientation,
+                      tHeader->config.uiMode,
+                      tHeader->config.touchscreen,
+                      tHeader->config.density,
+                      tHeader->config.keyboard,
+                      tHeader->config.inputFlags,
+                      tHeader->config.navigation,
+                      tHeader->config.screenWidth,
+                      tHeader->config.screenHeight,
+                      tHeader->config.smallestScreenWidthDp,
+                      tHeader->config.screenWidthDp,
+                      tHeader->config.screenHeightDp,
+                      tHeader->config.layoutDirection));
+                tHeader->config.swapHtoD();
+
+                // Build the entries inside of this type.
+                for (size_t ei=0; ei<N; ei++) {
+                    sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+                    sp<Entry> e = cl->getEntries().valueFor(config);
+
+                    // Set the offset for this entry in its type.
+                    uint32_t* index = (uint32_t*)
+                        (((uint8_t*)data->editData())
+                            + typeStart + sizeof(ResTable_type));
+                    if (e != NULL) {
+                        index[ei] = htodl(data->getSize()-typeStart-typeSize);
+
+                        // Create the entry.
+                        ssize_t amt = e->flatten(bundle, data, cl->getPublic());
+                        if (amt < 0) {
+                            return amt;
+                        }
+                        validResources.editItemAt(ei) = true;
+                    } else {
+                        index[ei] = htodl(ResTable_type::NO_ENTRY);
+                    }
+                }
+
+                // Fill in the rest of the type information.
+                tHeader = (ResTable_type*)
+                    (((uint8_t*)data->editData()) + typeStart);
+                tHeader->header.size = htodl(data->getSize()-typeStart);
+            }
+
+            for (size_t i = 0; i < N; ++i) {
+                if (!validResources[i]) {
+                    sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
+                    fprintf(stderr, "warning: no entries written for %s/%s\n",
+                            String8(typeName).string(), String8(c->getName()).string());
+                }
+            }
+        }
+
+        // Fill in the rest of the package information.
+        header = (ResTable_package*)data->editData();
+        header->header.size = htodl(data->getSize());
+        header->typeStrings = htodl(typeStringsStart);
+        header->lastPublicType = htodl(p->getTypeStrings().size());
+        header->keyStrings = htodl(keyStringsStart);
+        header->lastPublicKey = htodl(p->getKeyStrings().size());
+
+        flatPackages.add(data);
+    }
+
+    // And now write out the final chunks.
+    const size_t dataStart = dest->getSize();
+
+    {
+        // blah
+        ResTable_header header;
+        memset(&header, 0, sizeof(header));
+        header.header.type = htods(RES_TABLE_TYPE);
+        header.header.headerSize = htods(sizeof(header));
+        header.packageCount = htodl(flatPackages.size());
+        status_t err = dest->writeData(&header, sizeof(header));
+        if (err != NO_ERROR) {
+            fprintf(stderr, "ERROR: out of memory creating ResTable_header\n");
+            return err;
+        }
+    }
+    
+    ssize_t strStart = dest->getSize();
+    err = valueStrings.writeStringBlock(dest);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    ssize_t amt = (dest->getSize()-strStart);
+    strAmt += amt;
+    #if PRINT_STRING_METRICS
+    fprintf(stderr, "**** value strings: %d\n", amt);
+    fprintf(stderr, "**** total strings: %d\n", strAmt);
+    #endif
+    
+    for (pi=0; pi<flatPackages.size(); pi++) {
+        err = dest->writeData(flatPackages[pi]->getData(),
+                              flatPackages[pi]->getSize());
+        if (err != NO_ERROR) {
+            fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n");
+            return err;
+        }
+    }
+
+    ResTable_header* header = (ResTable_header*)
+        (((uint8_t*)dest->getData()) + dataStart);
+    header->header.size = htodl(dest->getSize() - dataStart);
+
+    NOISY(aout << "Resource table:"
+          << HexDump(dest->getData(), dest->getSize()) << endl);
+
+    #if PRINT_STRING_METRICS
+    fprintf(stderr, "**** total resource table size: %d / %d%% strings\n",
+        dest->getSize(), (strAmt*100)/dest->getSize());
+    #endif
+    
+    return NO_ERROR;
+}
+
+void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp)
+{
+    fprintf(fp,
+    "<!-- This file contains <public> resource definitions for all\n"
+    "     resources that were generated from the source data. -->\n"
+    "\n"
+    "<resources>\n");
+
+    writePublicDefinitions(package, fp, true);
+    writePublicDefinitions(package, fp, false);
+
+    fprintf(fp,
+    "\n"
+    "</resources>\n");
+}
+
+void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub)
+{
+    bool didHeader = false;
+
+    sp<Package> pkg = mPackages.valueFor(package);
+    if (pkg != NULL) {
+        const size_t NT = pkg->getOrderedTypes().size();
+        for (size_t i=0; i<NT; i++) {
+            sp<Type> t = pkg->getOrderedTypes().itemAt(i);
+            if (t == NULL) {
+                continue;
+            }
+
+            bool didType = false;
+
+            const size_t NC = t->getOrderedConfigs().size();
+            for (size_t j=0; j<NC; j++) {
+                sp<ConfigList> c = t->getOrderedConfigs().itemAt(j);
+                if (c == NULL) {
+                    continue;
+                }
+
+                if (c->getPublic() != pub) {
+                    continue;
+                }
+
+                if (!didType) {
+                    fprintf(fp, "\n");
+                    didType = true;
+                }
+                if (!didHeader) {
+                    if (pub) {
+                        fprintf(fp,"  <!-- PUBLIC SECTION.  These resources have been declared public.\n");
+                        fprintf(fp,"       Changes to these definitions will break binary compatibility. -->\n\n");
+                    } else {
+                        fprintf(fp,"  <!-- PRIVATE SECTION.  These resources have not been declared public.\n");
+                        fprintf(fp,"       You can make them public my moving these lines into a file in res/values. -->\n\n");
+                    }
+                    didHeader = true;
+                }
+                if (!pub) {
+                    const size_t NE = c->getEntries().size();
+                    for (size_t k=0; k<NE; k++) {
+                        const SourcePos& pos = c->getEntries().valueAt(k)->getPos();
+                        if (pos.file != "") {
+                            fprintf(fp,"  <!-- Declared at %s:%d -->\n",
+                                    pos.file.string(), pos.line);
+                        }
+                    }
+                }
+                fprintf(fp, "  <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n",
+                        String8(t->getName()).string(),
+                        String8(c->getName()).string(),
+                        getResId(pkg, t, c->getEntryIndex()));
+            }
+        }
+    }
+}
+
+ResourceTable::Item::Item(const SourcePos& _sourcePos,
+                          bool _isId,
+                          const String16& _value,
+                          const Vector<StringPool::entry_style_span>* _style,
+                          int32_t _format)
+    : sourcePos(_sourcePos)
+    , isId(_isId)
+    , value(_value)
+    , format(_format)
+    , bagKeyId(0)
+    , evaluating(false)
+{
+    if (_style) {
+        style = *_style;
+    }
+}
+
+status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos)
+{
+    if (mType == TYPE_BAG) {
+        return NO_ERROR;
+    }
+    if (mType == TYPE_UNKNOWN) {
+        mType = TYPE_BAG;
+        return NO_ERROR;
+    }
+    sourcePos.error("Resource entry %s is already defined as a single item.\n"
+                    "%s:%d: Originally defined here.\n",
+                    String8(mName).string(),
+                    mItem.sourcePos.file.string(), mItem.sourcePos.line);
+    return UNKNOWN_ERROR;
+}
+
+status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
+                                       const String16& value,
+                                       const Vector<StringPool::entry_style_span>* style,
+                                       int32_t format,
+                                       const bool overwrite)
+{
+    Item item(sourcePos, false, value, style);
+
+    if (mType == TYPE_BAG) {
+        const Item& item(mBag.valueAt(0));
+        sourcePos.error("Resource entry %s is already defined as a bag.\n"
+                        "%s:%d: Originally defined here.\n",
+                        String8(mName).string(),
+                        item.sourcePos.file.string(), item.sourcePos.line);
+        return UNKNOWN_ERROR;
+    }
+    if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
+        sourcePos.error("Resource entry %s is already defined.\n"
+                        "%s:%d: Originally defined here.\n",
+                        String8(mName).string(),
+                        mItem.sourcePos.file.string(), mItem.sourcePos.line);
+        return UNKNOWN_ERROR;
+    }
+
+    mType = TYPE_ITEM;
+    mItem = item;
+    mItemFormat = format;
+    return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos,
+                                        const String16& key, const String16& value,
+                                        const Vector<StringPool::entry_style_span>* style,
+                                        bool replace, bool isId, int32_t format)
+{
+    status_t err = makeItABag(sourcePos);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    Item item(sourcePos, isId, value, style, format);
+    
+    // XXX NOTE: there is an error if you try to have a bag with two keys,
+    // one an attr and one an id, with the same name.  Not something we
+    // currently ever have to worry about.
+    ssize_t origKey = mBag.indexOfKey(key);
+    if (origKey >= 0) {
+        if (!replace) {
+            const Item& item(mBag.valueAt(origKey));
+            sourcePos.error("Resource entry %s already has bag item %s.\n"
+                    "%s:%d: Originally defined here.\n",
+                    String8(mName).string(), String8(key).string(),
+                    item.sourcePos.file.string(), item.sourcePos.line);
+            return UNKNOWN_ERROR;
+        }
+        //printf("Replacing %s with %s\n",
+        //       String8(mBag.valueFor(key).value).string(), String8(value).string());
+        mBag.replaceValueFor(key, item);
+    }
+
+    mBag.add(key, item);
+    return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos)
+{
+    status_t err = makeItABag(sourcePos);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    mBag.clear();
+    return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::generateAttributes(ResourceTable* table,
+                                                  const String16& package)
+{
+    const String16 attr16("attr");
+    const String16 id16("id");
+    const size_t N = mBag.size();
+    for (size_t i=0; i<N; i++) {
+        const String16& key = mBag.keyAt(i);
+        const Item& it = mBag.valueAt(i);
+        if (it.isId) {
+            if (!table->hasBagOrEntry(key, &id16, &package)) {
+                String16 value("false");
+                status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package,
+                                               id16, key, value);
+                if (err != NO_ERROR) {
+                    return err;
+                }
+            }
+        } else if (!table->hasBagOrEntry(key, &attr16, &package)) {
+
+#if 1
+//             fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n",
+//                     String8(key).string());
+//             const Item& item(mBag.valueAt(i));
+//             fprintf(stderr, "Referenced from file %s line %d\n",
+//                     item.sourcePos.file.string(), item.sourcePos.line);
+//             return UNKNOWN_ERROR;
+#else
+            char numberStr[16];
+            sprintf(numberStr, "%d", ResTable_map::TYPE_ANY);
+            status_t err = table->addBag(SourcePos("<generated>", 0), package,
+                                         attr16, key, String16(""),
+                                         String16("^type"),
+                                         String16(numberStr), NULL, NULL);
+            if (err != NO_ERROR) {
+                return err;
+            }
+#endif
+        }
+    }
+    return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table,
+                                                 const String16& package)
+{
+    bool hasErrors = false;
+    
+    if (mType == TYPE_BAG) {
+        const char* errorMsg;
+        const String16 style16("style");
+        const String16 attr16("attr");
+        const String16 id16("id");
+        mParentId = 0;
+        if (mParent.size() > 0) {
+            mParentId = table->getResId(mParent, &style16, NULL, &errorMsg);
+            if (mParentId == 0) {
+                mPos.error("Error retrieving parent for item: %s '%s'.\n",
+                        errorMsg, String8(mParent).string());
+                hasErrors = true;
+            }
+        }
+        const size_t N = mBag.size();
+        for (size_t i=0; i<N; i++) {
+            const String16& key = mBag.keyAt(i);
+            Item& it = mBag.editValueAt(i);
+            it.bagKeyId = table->getResId(key,
+                    it.isId ? &id16 : &attr16, NULL, &errorMsg);
+            //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId);
+            if (it.bagKeyId == 0) {
+                it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg,
+                        String8(it.isId ? id16 : attr16).string(),
+                        String8(key).string());
+                hasErrors = true;
+            }
+        }
+    }
+    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table,
+        const String8* configTypeName, const ConfigDescription* config)
+{
+    if (mType == TYPE_ITEM) {
+        Item& it = mItem;
+        AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value));
+        if (!table->stringToValue(&it.parsedValue, strings,
+                                  it.value, false, true, 0,
+                                  &it.style, NULL, &ac, mItemFormat,
+                                  configTypeName, config)) {
+            return UNKNOWN_ERROR;
+        }
+    } else if (mType == TYPE_BAG) {
+        const size_t N = mBag.size();
+        for (size_t i=0; i<N; i++) {
+            const String16& key = mBag.keyAt(i);
+            Item& it = mBag.editValueAt(i);
+            AccessorCookie ac(it.sourcePos, String8(key), String8(it.value));
+            if (!table->stringToValue(&it.parsedValue, strings,
+                                      it.value, false, true, it.bagKeyId,
+                                      &it.style, NULL, &ac, it.format,
+                                      configTypeName, config)) {
+                return UNKNOWN_ERROR;
+            }
+        }
+    } else {
+        mPos.error("Error: entry %s is not a single item or a bag.\n",
+                   String8(mName).string());
+        return UNKNOWN_ERROR;
+    }
+    return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::remapStringValue(StringPool* strings)
+{
+    if (mType == TYPE_ITEM) {
+        Item& it = mItem;
+        if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
+            it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
+        }
+    } else if (mType == TYPE_BAG) {
+        const size_t N = mBag.size();
+        for (size_t i=0; i<N; i++) {
+            Item& it = mBag.editValueAt(i);
+            if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
+                it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
+            }
+        }
+    } else {
+        mPos.error("Error: entry %s is not a single item or a bag.\n",
+                   String8(mName).string());
+        return UNKNOWN_ERROR;
+    }
+    return NO_ERROR;
+}
+
+ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp<AaptFile>& data, bool isPublic)
+{
+    size_t amt = 0;
+    ResTable_entry header;
+    memset(&header, 0, sizeof(header));
+    header.size = htods(sizeof(header));
+    const type ty = this != NULL ? mType : TYPE_ITEM;
+    if (this != NULL) {
+        if (ty == TYPE_BAG) {
+            header.flags |= htods(header.FLAG_COMPLEX);
+        }
+        if (isPublic) {
+            header.flags |= htods(header.FLAG_PUBLIC);
+        }
+        header.key.index = htodl(mNameIndex);
+    }
+    if (ty != TYPE_BAG) {
+        status_t err = data->writeData(&header, sizeof(header));
+        if (err != NO_ERROR) {
+            fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
+            return err;
+        }
+
+        const Item& it = mItem;
+        Res_value par;
+        memset(&par, 0, sizeof(par));
+        par.size = htods(it.parsedValue.size);
+        par.dataType = it.parsedValue.dataType;
+        par.res0 = it.parsedValue.res0;
+        par.data = htodl(it.parsedValue.data);
+        #if 0
+        printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n",
+               String8(mName).string(), it.parsedValue.dataType,
+               it.parsedValue.data, par.res0);
+        #endif
+        err = data->writeData(&par, it.parsedValue.size);
+        if (err != NO_ERROR) {
+            fprintf(stderr, "ERROR: out of memory creating Res_value\n");
+            return err;
+        }
+        amt += it.parsedValue.size;
+    } else {
+        size_t N = mBag.size();
+        size_t i;
+        // Create correct ordering of items.
+        KeyedVector<uint32_t, const Item*> items;
+        for (i=0; i<N; i++) {
+            const Item& it = mBag.valueAt(i);
+            items.add(it.bagKeyId, &it);
+        }
+        N = items.size();
+        
+        ResTable_map_entry mapHeader;
+        memcpy(&mapHeader, &header, sizeof(header));
+        mapHeader.size = htods(sizeof(mapHeader));
+        mapHeader.parent.ident = htodl(mParentId);
+        mapHeader.count = htodl(N);
+        status_t err = data->writeData(&mapHeader, sizeof(mapHeader));
+        if (err != NO_ERROR) {
+            fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
+            return err;
+        }
+
+        for (i=0; i<N; i++) {
+            const Item& it = *items.valueAt(i);
+            ResTable_map map;
+            map.name.ident = htodl(it.bagKeyId);
+            map.value.size = htods(it.parsedValue.size);
+            map.value.dataType = it.parsedValue.dataType;
+            map.value.res0 = it.parsedValue.res0;
+            map.value.data = htodl(it.parsedValue.data);
+            err = data->writeData(&map, sizeof(map));
+            if (err != NO_ERROR) {
+                fprintf(stderr, "ERROR: out of memory creating Res_value\n");
+                return err;
+            }
+            amt += sizeof(map);
+        }
+    }
+    return amt;
+}
+
+void ResourceTable::ConfigList::appendComment(const String16& comment,
+                                              bool onlyIfEmpty)
+{
+    if (comment.size() <= 0) {
+        return;
+    }
+    if (onlyIfEmpty && mComment.size() > 0) {
+        return;
+    }
+    if (mComment.size() > 0) {
+        mComment.append(String16("\n"));
+    }
+    mComment.append(comment);
+}
+
+void ResourceTable::ConfigList::appendTypeComment(const String16& comment)
+{
+    if (comment.size() <= 0) {
+        return;
+    }
+    if (mTypeComment.size() > 0) {
+        mTypeComment.append(String16("\n"));
+    }
+    mTypeComment.append(comment);
+}
+
+status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos,
+                                        const String16& name,
+                                        const uint32_t ident)
+{
+    #if 0
+    int32_t entryIdx = Res_GETENTRY(ident);
+    if (entryIdx < 0) {
+        sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n",
+                String8(mName).string(), String8(name).string(), ident);
+        return UNKNOWN_ERROR;
+    }
+    #endif
+
+    int32_t typeIdx = Res_GETTYPE(ident);
+    if (typeIdx >= 0) {
+        typeIdx++;
+        if (mPublicIndex > 0 && mPublicIndex != typeIdx) {
+            sourcePos.error("Public resource %s/%s has conflicting type codes for its"
+                    " public identifiers (0x%x vs 0x%x).\n",
+                    String8(mName).string(), String8(name).string(),
+                    mPublicIndex, typeIdx);
+            return UNKNOWN_ERROR;
+        }
+        mPublicIndex = typeIdx;
+    }
+
+    if (mFirstPublicSourcePos == NULL) {
+        mFirstPublicSourcePos = new SourcePos(sourcePos);
+    }
+
+    if (mPublic.indexOfKey(name) < 0) {
+        mPublic.add(name, Public(sourcePos, String16(), ident));
+    } else {
+        Public& p = mPublic.editValueFor(name);
+        if (p.ident != ident) {
+            sourcePos.error("Public resource %s/%s has conflicting public identifiers"
+                    " (0x%08x vs 0x%08x).\n"
+                    "%s:%d: Originally defined here.\n",
+                    String8(mName).string(), String8(name).string(), p.ident, ident,
+                    p.sourcePos.file.string(), p.sourcePos.line);
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+void ResourceTable::Type::canAddEntry(const String16& name)
+{
+    mCanAddEntries.add(name);
+}
+
+sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry,
+                                                       const SourcePos& sourcePos,
+                                                       const ResTable_config* config,
+                                                       bool doSetIndex,
+                                                       bool overlay,
+                                                       bool autoAddOverlay)
+{
+    int pos = -1;
+    sp<ConfigList> c = mConfigs.valueFor(entry);
+    if (c == NULL) {
+        if (overlay && !autoAddOverlay && mCanAddEntries.indexOf(entry) < 0) {
+            sourcePos.error("Resource at %s appears in overlay but not"
+                            " in the base package; use <add-resource> to add.\n",
+                            String8(entry).string());
+            return NULL;
+        }
+        c = new ConfigList(entry, sourcePos);
+        mConfigs.add(entry, c);
+        pos = (int)mOrderedConfigs.size();
+        mOrderedConfigs.add(c);
+        if (doSetIndex) {
+            c->setEntryIndex(pos);
+        }
+    }
+    
+    ConfigDescription cdesc;
+    if (config) cdesc = *config;
+    
+    sp<Entry> e = c->getEntries().valueFor(cdesc);
+    if (e == NULL) {
+        if (config != NULL) {
+            NOISY(printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c "
+                    "orien:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
+                    "sw%ddp w%ddp h%ddp dir:%d\n",
+                      sourcePos.file.string(), sourcePos.line,
+                      config->mcc, config->mnc,
+                      config->language[0] ? config->language[0] : '-',
+                      config->language[1] ? config->language[1] : '-',
+                      config->country[0] ? config->country[0] : '-',
+                      config->country[1] ? config->country[1] : '-',
+                      config->orientation,
+                      config->touchscreen,
+                      config->density,
+                      config->keyboard,
+                      config->inputFlags,
+                      config->navigation,
+                      config->screenWidth,
+                      config->screenHeight,
+                      config->smallestScreenWidthDp,
+                      config->screenWidthDp,
+                      config->screenHeightDp,
+                      config->layoutDirection));
+        } else {
+            NOISY(printf("New entry at %s:%d: NULL config\n",
+                      sourcePos.file.string(), sourcePos.line));
+        }
+        e = new Entry(entry, sourcePos);
+        c->addEntry(cdesc, e);
+        /*
+        if (doSetIndex) {
+            if (pos < 0) {
+                for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) {
+                    if (mOrderedConfigs[pos] == c) {
+                        break;
+                    }
+                }
+                if (pos >= (int)mOrderedConfigs.size()) {
+                    sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry");
+                    return NULL;
+                }
+            }
+            e->setEntryIndex(pos);
+        }
+        */
+    }
+    
+    mUniqueConfigs.add(cdesc);
+    
+    return e;
+}
+
+status_t ResourceTable::Type::applyPublicEntryOrder()
+{
+    size_t N = mOrderedConfigs.size();
+    Vector<sp<ConfigList> > origOrder(mOrderedConfigs);
+    bool hasError = false;
+
+    size_t i;
+    for (i=0; i<N; i++) {
+        mOrderedConfigs.replaceAt(NULL, i);
+    }
+
+    const size_t NP = mPublic.size();
+    //printf("Ordering %d configs from %d public defs\n", N, NP);
+    size_t j;
+    for (j=0; j<NP; j++) {
+        const String16& name = mPublic.keyAt(j);
+        const Public& p = mPublic.valueAt(j);
+        int32_t idx = Res_GETENTRY(p.ident);
+        //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n",
+        //       String8(mName).string(), String8(name).string(), p.ident, N);
+        bool found = false;
+        for (i=0; i<N; i++) {
+            sp<ConfigList> e = origOrder.itemAt(i);
+            //printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
+            if (e->getName() == name) {
+                if (idx >= (int32_t)mOrderedConfigs.size()) {
+                    p.sourcePos.error("Public entry identifier 0x%x entry index "
+                            "is larger than available symbols (index %d, total symbols %d).\n",
+                            p.ident, idx, mOrderedConfigs.size());
+                    hasError = true;
+                } else if (mOrderedConfigs.itemAt(idx) == NULL) {
+                    e->setPublic(true);
+                    e->setPublicSourcePos(p.sourcePos);
+                    mOrderedConfigs.replaceAt(e, idx);
+                    origOrder.removeAt(i);
+                    N--;
+                    found = true;
+                    break;
+                } else {
+                    sp<ConfigList> oe = mOrderedConfigs.itemAt(idx);
+
+                    p.sourcePos.error("Multiple entry names declared for public entry"
+                            " identifier 0x%x in type %s (%s vs %s).\n"
+                            "%s:%d: Originally defined here.",
+                            idx+1, String8(mName).string(),
+                            String8(oe->getName()).string(),
+                            String8(name).string(),
+                            oe->getPublicSourcePos().file.string(),
+                            oe->getPublicSourcePos().line);
+                    hasError = true;
+                }
+            }
+        }
+
+        if (!found) {
+            p.sourcePos.error("Public symbol %s/%s declared here is not defined.",
+                    String8(mName).string(), String8(name).string());
+            hasError = true;
+        }
+    }
+
+    //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size());
+    
+    if (N != origOrder.size()) {
+        printf("Internal error: remaining private symbol count mismatch\n");
+        N = origOrder.size();
+    }
+    
+    j = 0;
+    for (i=0; i<N; i++) {
+        sp<ConfigList> e = origOrder.itemAt(i);
+        // There will always be enough room for the remaining entries.
+        while (mOrderedConfigs.itemAt(j) != NULL) {
+            j++;
+        }
+        mOrderedConfigs.replaceAt(e, j);
+        j++;
+    }
+
+    return hasError ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+ResourceTable::Package::Package(const String16& name, ssize_t includedId)
+    : mName(name), mIncludedId(includedId),
+      mTypeStringsMapping(0xffffffff),
+      mKeyStringsMapping(0xffffffff)
+{
+}
+
+sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type,
+                                                        const SourcePos& sourcePos,
+                                                        bool doSetIndex)
+{
+    sp<Type> t = mTypes.valueFor(type);
+    if (t == NULL) {
+        t = new Type(type, sourcePos);
+        mTypes.add(type, t);
+        mOrderedTypes.add(t);
+        if (doSetIndex) {
+            // For some reason the type's index is set to one plus the index
+            // in the mOrderedTypes list, rather than just the index.
+            t->setIndex(mOrderedTypes.size());
+        }
+    }
+    return t;
+}
+
+status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data)
+{
+    mTypeStringsData = data;
+    status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "ERROR: Type string data is corrupt!\n");
+    }
+    return err;
+}
+
+status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data)
+{
+    mKeyStringsData = data;
+    status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "ERROR: Key string data is corrupt!\n");
+    }
+    return err;
+}
+
+status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data,
+                                            ResStringPool* strings,
+                                            DefaultKeyedVector<String16, uint32_t>* mappings)
+{
+    if (data->getData() == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    NOISY(aout << "Setting restable string pool: "
+          << HexDump(data->getData(), data->getSize()) << endl);
+
+    status_t err = strings->setTo(data->getData(), data->getSize());
+    if (err == NO_ERROR) {
+        const size_t N = strings->size();
+        for (size_t i=0; i<N; i++) {
+            size_t len;
+            mappings->add(String16(strings->stringAt(i, &len)), i);
+        }
+    }
+    return err;
+}
+
+status_t ResourceTable::Package::applyPublicTypeOrder()
+{
+    size_t N = mOrderedTypes.size();
+    Vector<sp<Type> > origOrder(mOrderedTypes);
+
+    size_t i;
+    for (i=0; i<N; i++) {
+        mOrderedTypes.replaceAt(NULL, i);
+    }
+
+    for (i=0; i<N; i++) {
+        sp<Type> t = origOrder.itemAt(i);
+        int32_t idx = t->getPublicIndex();
+        if (idx > 0) {
+            idx--;
+            while (idx >= (int32_t)mOrderedTypes.size()) {
+                mOrderedTypes.add();
+            }
+            if (mOrderedTypes.itemAt(idx) != NULL) {
+                sp<Type> ot = mOrderedTypes.itemAt(idx);
+                t->getFirstPublicSourcePos().error("Multiple type names declared for public type"
+                        " identifier 0x%x (%s vs %s).\n"
+                        "%s:%d: Originally defined here.",
+                        idx, String8(ot->getName()).string(),
+                        String8(t->getName()).string(),
+                        ot->getFirstPublicSourcePos().file.string(),
+                        ot->getFirstPublicSourcePos().line);
+                return UNKNOWN_ERROR;
+            }
+            mOrderedTypes.replaceAt(t, idx);
+            origOrder.removeAt(i);
+            i--;
+            N--;
+        }
+    }
+
+    size_t j=0;
+    for (i=0; i<N; i++) {
+        sp<Type> t = origOrder.itemAt(i);
+        // There will always be enough room for the remaining types.
+        while (mOrderedTypes.itemAt(j) != NULL) {
+            j++;
+        }
+        mOrderedTypes.replaceAt(t, j);
+    }
+
+    return NO_ERROR;
+}
+
+sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
+{
+    sp<Package> p = mPackages.valueFor(package);
+    if (p == NULL) {
+        if (mIsAppPackage) {
+            if (mHaveAppPackage) {
+                fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n"
+                                "Use -x to create extended resources.\n");
+                return NULL;
+            }
+            mHaveAppPackage = true;
+            p = new Package(package, 127);
+        } else {
+            p = new Package(package, mNextPackageId);
+        }
+        //printf("*** NEW PACKAGE: \"%s\" id=%d\n",
+        //       String8(package).string(), p->getAssignedId());
+        mPackages.add(package, p);
+        mOrderedPackages.add(p);
+        mNextPackageId++;
+    }
+    return p;
+}
+
+sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
+                                               const String16& type,
+                                               const SourcePos& sourcePos,
+                                               bool doSetIndex)
+{
+    sp<Package> p = getPackage(package);
+    if (p == NULL) {
+        return NULL;
+    }
+    return p->getType(type, sourcePos, doSetIndex);
+}
+
+sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
+                                                 const String16& type,
+                                                 const String16& name,
+                                                 const SourcePos& sourcePos,
+                                                 bool overlay,
+                                                 const ResTable_config* config,
+                                                 bool doSetIndex)
+{
+    sp<Type> t = getType(package, type, sourcePos, doSetIndex);
+    if (t == NULL) {
+        return NULL;
+    }
+    return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
+}
+
+sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
+                                                       const ResTable_config* config) const
+{
+    int pid = Res_GETPACKAGE(resID)+1;
+    const size_t N = mOrderedPackages.size();
+    size_t i;
+    sp<Package> p;
+    for (i=0; i<N; i++) {
+        sp<Package> check = mOrderedPackages[i];
+        if (check->getAssignedId() == pid) {
+            p = check;
+            break;
+        }
+
+    }
+    if (p == NULL) {
+        fprintf(stderr, "warning: Package not found for resource #%08x\n", resID);
+        return NULL;
+    }
+
+    int tid = Res_GETTYPE(resID);
+    if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) {
+        fprintf(stderr, "warning: Type not found for resource #%08x\n", resID);
+        return NULL;
+    }
+    sp<Type> t = p->getOrderedTypes()[tid];
+
+    int eid = Res_GETENTRY(resID);
+    if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) {
+        fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
+        return NULL;
+    }
+
+    sp<ConfigList> c = t->getOrderedConfigs()[eid];
+    if (c == NULL) {
+        fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
+        return NULL;
+    }
+    
+    ConfigDescription cdesc;
+    if (config) cdesc = *config;
+    sp<Entry> e = c->getEntries().valueFor(cdesc);
+    if (c == NULL) {
+        fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID);
+        return NULL;
+    }
+    
+    return e;
+}
+
+const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const
+{
+    sp<const Entry> e = getEntry(resID);
+    if (e == NULL) {
+        return NULL;
+    }
+
+    const size_t N = e->getBag().size();
+    for (size_t i=0; i<N; i++) {
+        const Item& it = e->getBag().valueAt(i);
+        if (it.bagKeyId == 0) {
+            fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n",
+                    String8(e->getName()).string(),
+                    String8(e->getBag().keyAt(i)).string());
+        }
+        if (it.bagKeyId == attrID) {
+            return &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..a3e0666
--- /dev/null
+++ b/tools/aapt/ResourceTable.h
@@ -0,0 +1,557 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef RESOURCE_TABLE_H
+#define RESOURCE_TABLE_H
+
+#include "StringPool.h"
+#include "SourcePos.h"
+
+#include <set>
+#include <map>
+
+using namespace std;
+
+class XMLNode;
+class ResourceTable;
+
+enum {
+    XML_COMPILE_STRIP_COMMENTS = 1<<0,
+    XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
+    XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
+    XML_COMPILE_STRIP_WHITESPACE = 1<<3,
+    XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
+    XML_COMPILE_UTF8 = 1<<5,
+    
+    XML_COMPILE_STANDARD_RESOURCE =
+            XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+            | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
+};
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+                        const sp<AaptFile>& target,
+                        ResourceTable* table,
+                        int options = XML_COMPILE_STANDARD_RESOURCE);
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+                        const sp<AaptFile>& target,
+                        const sp<AaptFile>& outTarget,
+                        ResourceTable* table,
+                        int options = XML_COMPILE_STANDARD_RESOURCE);
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+                        const sp<XMLNode>& xmlTree,
+                        const sp<AaptFile>& target,
+                        ResourceTable* table,
+                        int options = XML_COMPILE_STANDARD_RESOURCE);
+
+status_t compileResourceFile(Bundle* bundle,
+                             const sp<AaptAssets>& assets,
+                             const sp<AaptFile>& in,
+                             const ResTable_config& defParams,
+                             const bool overwrite,
+                             ResourceTable* outTable);
+
+struct AccessorCookie
+{
+    SourcePos sourcePos;
+    String8 attr;
+    String8 value;
+
+    AccessorCookie(const SourcePos&p, const String8& a, const String8& v)
+        :sourcePos(p),
+         attr(a),
+         value(v)
+    {
+    }
+};
+
+class ResourceTable : public ResTable::Accessor
+{
+public:
+    class Package;
+    class Type;
+    class Entry;
+
+    struct ConfigDescription : public ResTable_config {
+        ConfigDescription() {
+            memset(this, 0, sizeof(*this));
+            size = sizeof(ResTable_config);
+        }
+        ConfigDescription(const ResTable_config&o) {
+            *static_cast<ResTable_config*>(this) = o;
+            size = sizeof(ResTable_config);
+        }
+        ConfigDescription(const ConfigDescription&o) {
+            *static_cast<ResTable_config*>(this) = o;
+        }
+
+        ConfigDescription& operator=(const ResTable_config& o) {
+            *static_cast<ResTable_config*>(this) = o;
+            size = sizeof(ResTable_config);
+            return *this;
+        }
+        ConfigDescription& operator=(const ConfigDescription& o) {
+            *static_cast<ResTable_config*>(this) = o;
+            return *this;
+        }
+
+        inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; }
+        inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; }
+        inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; }
+        inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; }
+        inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; }
+        inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; }
+    };
+
+    ResourceTable(Bundle* bundle, const String16& assetsPackage);
+
+    status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets);
+
+    status_t addPublic(const SourcePos& pos,
+                       const String16& package,
+                       const String16& type,
+                       const String16& name,
+                       const uint32_t ident);
+
+    status_t addEntry(const SourcePos& pos,
+                      const String16& package,
+                      const String16& type,
+                      const String16& name,
+                      const String16& value,
+                      const Vector<StringPool::entry_style_span>* style = NULL,
+                      const ResTable_config* params = NULL,
+                      const bool doSetIndex = false,
+                      const int32_t format = ResTable_map::TYPE_ANY,
+                      const bool overwrite = false);
+
+    status_t startBag(const SourcePos& pos,
+                    const String16& package,
+                    const String16& type,
+                    const String16& name,
+                    const String16& bagParent,
+                    const ResTable_config* params = NULL,
+                    bool overlay = false,
+                    bool replace = false,
+                    bool isId = false);
+    
+    status_t addBag(const SourcePos& pos,
+                    const String16& package,
+                    const String16& type,
+                    const String16& name,
+                    const String16& bagParent,
+                    const String16& bagKey,
+                    const String16& value,
+                    const Vector<StringPool::entry_style_span>* style = NULL,
+                    const ResTable_config* params = NULL,
+                    bool replace = false,
+                    bool isId = false,
+                    const int32_t format = ResTable_map::TYPE_ANY);
+
+    bool hasBagOrEntry(const String16& package,
+                       const String16& type,
+                       const String16& name) const;
+
+    bool hasBagOrEntry(const String16& package,
+                       const String16& type,
+                       const String16& name,
+                       const ResTable_config& config) const;
+
+    bool hasBagOrEntry(const String16& ref,
+                       const String16* defType = NULL,
+                       const String16* defPackage = NULL);
+
+    bool appendComment(const String16& package,
+                       const String16& type,
+                       const String16& name,
+                       const String16& comment,
+                       bool onlyIfEmpty = false);
+
+    bool appendTypeComment(const String16& package,
+                           const String16& type,
+                           const String16& name,
+                           const String16& comment);
+    
+    void canAddEntry(const SourcePos& pos,
+        const String16& package, const String16& type, const String16& name);
+        
+    size_t size() const;
+    size_t numLocalResources() const;
+    bool hasResources() const;
+
+    sp<AaptFile> flatten(Bundle*);
+
+    static inline uint32_t makeResId(uint32_t packageId,
+                                     uint32_t typeId,
+                                     uint32_t nameId)
+    {
+        return nameId | (typeId<<16) | (packageId<<24);
+    }
+
+    static inline uint32_t getResId(const sp<Package>& p,
+                                    const sp<Type>& t,
+                                    uint32_t nameId);
+
+    uint32_t getResId(const String16& package,
+                      const String16& type,
+                      const String16& name,
+                      bool onlyPublic = true) const;
+
+    uint32_t getResId(const String16& ref,
+                      const String16* defType = NULL,
+                      const String16* defPackage = NULL,
+                      const char** outErrorMsg = NULL,
+                      bool onlyPublic = true) const;
+
+    static bool isValidResourceName(const String16& s);
+    
+    bool stringToValue(Res_value* outValue, StringPool* pool,
+                       const String16& str,
+                       bool preserveSpaces, bool coerceType,
+                       uint32_t attrID,
+                       const Vector<StringPool::entry_style_span>* style = NULL,
+                       String16* outStr = NULL, void* accessorCookie = NULL,
+                       uint32_t attrType = ResTable_map::TYPE_ANY,
+                       const String8* configTypeName = NULL,
+                       const ConfigDescription* config = NULL);
+
+    status_t assignResourceIds();
+    status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL);
+    void addLocalization(const String16& name, const String8& locale);
+    status_t validateLocalizations(void);
+
+    status_t flatten(Bundle*, const sp<AaptFile>& dest);
+
+    void writePublicDefinitions(const String16& package, FILE* fp);
+
+    virtual uint32_t getCustomResource(const String16& package,
+                                       const String16& type,
+                                       const String16& name) const;
+    virtual uint32_t getCustomResourceWithCreation(const String16& package,
+                                                   const String16& type,
+                                                   const String16& name,
+                                                   const bool createIfNeeded);
+    virtual uint32_t getRemappedPackage(uint32_t origPackage) const;
+    virtual bool getAttributeType(uint32_t attrID, uint32_t* outType);
+    virtual bool getAttributeMin(uint32_t attrID, uint32_t* outMin);
+    virtual bool getAttributeMax(uint32_t attrID, uint32_t* outMax);
+    virtual bool getAttributeKeys(uint32_t attrID, Vector<String16>* outKeys);
+    virtual bool getAttributeEnum(uint32_t attrID,
+                                  const char16_t* name, size_t nameLen,
+                                  Res_value* outValue);
+    virtual bool getAttributeFlags(uint32_t attrID,
+                                   const char16_t* name, size_t nameLen,
+                                   Res_value* outValue);
+    virtual uint32_t getAttributeL10N(uint32_t attrID);
+
+    virtual bool getLocalizationSetting();
+    virtual void reportError(void* accessorCookie, const char* fmt, ...);
+
+    void setCurrentXmlPos(const SourcePos& pos) { mCurrentXmlPos = pos; }
+
+    class Item {
+    public:
+        Item() : isId(false), format(ResTable_map::TYPE_ANY), bagKeyId(0), evaluating(false)
+            { memset(&parsedValue, 0, sizeof(parsedValue)); }
+        Item(const SourcePos& pos,
+             bool _isId,
+             const String16& _value,
+             const Vector<StringPool::entry_style_span>* _style = NULL,
+             int32_t format = ResTable_map::TYPE_ANY);
+        Item(const Item& o) : sourcePos(o.sourcePos),
+            isId(o.isId), value(o.value), style(o.style),
+            format(o.format), bagKeyId(o.bagKeyId), evaluating(false) {
+            memset(&parsedValue, 0, sizeof(parsedValue));
+        }
+        ~Item() { }
+
+        Item& operator=(const Item& o) {
+            sourcePos = o.sourcePos;
+            isId = o.isId;
+            value = o.value;
+            style = o.style;
+            format = o.format;
+            bagKeyId = o.bagKeyId;
+            parsedValue = o.parsedValue;
+            return *this;
+        }
+
+        SourcePos                               sourcePos;
+        mutable bool                            isId;
+        String16                                value;
+        Vector<StringPool::entry_style_span>    style;
+        int32_t                                 format;
+        uint32_t                                bagKeyId;
+        mutable bool                            evaluating;
+        Res_value                               parsedValue;
+    };
+
+    class Entry : public RefBase {
+    public:
+        Entry(const String16& name, const SourcePos& pos)
+            : mName(name), mType(TYPE_UNKNOWN),
+              mItemFormat(ResTable_map::TYPE_ANY), mNameIndex(-1), mPos(pos)
+        { }
+        virtual ~Entry() { }
+
+        enum type {
+            TYPE_UNKNOWN = 0,
+            TYPE_ITEM,
+            TYPE_BAG
+        };
+        
+        String16 getName() const { return mName; }
+        type getType() const { return mType; }
+
+        void setParent(const String16& parent) { mParent = parent; }
+        String16 getParent() const { return mParent; }
+
+        status_t makeItABag(const SourcePos& sourcePos);
+
+        status_t emptyBag(const SourcePos& sourcePos);
+ 
+        status_t setItem(const SourcePos& pos,
+                         const String16& value,
+                         const Vector<StringPool::entry_style_span>* style = NULL,
+                         int32_t format = ResTable_map::TYPE_ANY,
+                         const bool overwrite = false);
+
+        status_t addToBag(const SourcePos& pos,
+                          const String16& key, const String16& value,
+                          const Vector<StringPool::entry_style_span>* style = NULL,
+                          bool replace=false, bool isId = false,
+                          int32_t format = ResTable_map::TYPE_ANY);
+
+        // Index of the entry's name string in the key pool.
+        int32_t getNameIndex() const { return mNameIndex; }
+        void setNameIndex(int32_t index) { mNameIndex = index; }
+
+        const Item* getItem() const { return mType == TYPE_ITEM ? &mItem : NULL; }
+        const KeyedVector<String16, Item>& getBag() const { return mBag; }
+
+        status_t generateAttributes(ResourceTable* table,
+                                    const String16& package);
+
+        status_t assignResourceIds(ResourceTable* table,
+                                   const String16& package);
+
+        status_t prepareFlatten(StringPool* strings, ResourceTable* table,
+               const String8* configTypeName, const ConfigDescription* config);
+
+        status_t remapStringValue(StringPool* strings);
+
+        ssize_t flatten(Bundle*, const sp<AaptFile>& data, bool isPublic);
+
+        const SourcePos& getPos() const { return mPos; }
+
+    private:
+        String16 mName;
+        String16 mParent;
+        type mType;
+        Item mItem;
+        int32_t mItemFormat;
+        KeyedVector<String16, Item> mBag;
+        int32_t mNameIndex;
+        uint32_t mParentId;
+        SourcePos mPos;
+    };
+    
+    class ConfigList : public RefBase {
+    public:
+        ConfigList(const String16& name, const SourcePos& pos)
+            : mName(name), mPos(pos), mPublic(false), mEntryIndex(-1) { }
+        virtual ~ConfigList() { }
+        
+        String16 getName() const { return mName; }
+        const SourcePos& getPos() const { return mPos; }
+        
+        void appendComment(const String16& comment, bool onlyIfEmpty = false);
+        const String16& getComment() const { return mComment; }
+        
+        void appendTypeComment(const String16& comment);
+        const String16& getTypeComment() const { return mTypeComment; }
+        
+        // Index of this entry in its Type.
+        int32_t getEntryIndex() const { return mEntryIndex; }
+        void setEntryIndex(int32_t index) { mEntryIndex = index; }
+        
+        void setPublic(bool pub) { mPublic = pub; }
+        bool getPublic() const { return mPublic; }
+        void setPublicSourcePos(const SourcePos& pos) { mPublicSourcePos = pos; }
+        const SourcePos& getPublicSourcePos() { return mPublicSourcePos; }
+        
+        void addEntry(const ResTable_config& config, const sp<Entry>& entry) {
+            mEntries.add(config, entry);
+        }
+        
+        const DefaultKeyedVector<ConfigDescription, sp<Entry> >& getEntries() const { return mEntries; }
+    private:
+        const String16 mName;
+        const SourcePos mPos;
+        String16 mComment;
+        String16 mTypeComment;
+        bool mPublic;
+        SourcePos mPublicSourcePos;
+        int32_t mEntryIndex;
+        DefaultKeyedVector<ConfigDescription, sp<Entry> > mEntries;
+    };
+    
+    class Public {
+    public:
+        Public() : sourcePos(), ident(0) { }
+        Public(const SourcePos& pos,
+               const String16& _comment,
+               uint32_t _ident)
+            : sourcePos(pos),
+            comment(_comment), ident(_ident) { }
+        Public(const Public& o) : sourcePos(o.sourcePos),
+            comment(o.comment), ident(o.ident) { }
+        ~Public() { }
+        
+        Public& operator=(const Public& o) {
+            sourcePos = o.sourcePos;
+            comment = o.comment;
+            ident = o.ident;
+            return *this;
+        }
+        
+        SourcePos   sourcePos;
+        String16    comment;
+        uint32_t    ident;
+    };
+    
+    class Type : public RefBase {
+    public:
+        Type(const String16& name, const SourcePos& pos)
+                : mName(name), mFirstPublicSourcePos(NULL), mPublicIndex(-1), mIndex(-1), mPos(pos)
+        { }
+        virtual ~Type() { delete mFirstPublicSourcePos; }
+
+        status_t addPublic(const SourcePos& pos,
+                           const String16& name,
+                           const uint32_t ident);
+                           
+        void canAddEntry(const String16& name);
+        
+        String16 getName() const { return mName; }
+        sp<Entry> getEntry(const String16& entry,
+                           const SourcePos& pos,
+                           const ResTable_config* config = NULL,
+                           bool doSetIndex = false,
+                           bool overlay = false,
+                           bool autoAddOverlay = false);
+
+        const SourcePos& getFirstPublicSourcePos() const { return *mFirstPublicSourcePos; }
+
+        int32_t getPublicIndex() const { return mPublicIndex; }
+
+        int32_t getIndex() const { return mIndex; }
+        void setIndex(int32_t index) { mIndex = index; }
+
+        status_t applyPublicEntryOrder();
+
+        const SortedVector<ConfigDescription>& getUniqueConfigs() const { return mUniqueConfigs; }
+        
+        const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; }
+        const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; }
+
+        const SortedVector<String16>& getCanAddEntries() const { return mCanAddEntries; }
+        
+        const SourcePos& getPos() const { return mPos; }
+    private:
+        String16 mName;
+        SourcePos* mFirstPublicSourcePos;
+        DefaultKeyedVector<String16, Public> mPublic;
+        SortedVector<ConfigDescription> mUniqueConfigs;
+        DefaultKeyedVector<String16, sp<ConfigList> > mConfigs;
+        Vector<sp<ConfigList> > mOrderedConfigs;
+        SortedVector<String16> mCanAddEntries;
+        int32_t mPublicIndex;
+        int32_t mIndex;
+        SourcePos mPos;
+    };
+
+    class Package : public RefBase {
+    public:
+        Package(const String16& name, ssize_t includedId=-1);
+        virtual ~Package() { }
+
+        String16 getName() const { return mName; }
+        sp<Type> getType(const String16& type,
+                         const SourcePos& pos,
+                         bool doSetIndex = false);
+
+        ssize_t getAssignedId() const { return mIncludedId; }
+
+        const ResStringPool& getTypeStrings() const { return mTypeStrings; }
+        uint32_t indexOfTypeString(const String16& s) const { return mTypeStringsMapping.valueFor(s); }
+        const sp<AaptFile> getTypeStringsData() const { return mTypeStringsData; }
+        status_t setTypeStrings(const sp<AaptFile>& data);
+
+        const ResStringPool& getKeyStrings() const { return mKeyStrings; }
+        uint32_t indexOfKeyString(const String16& s) const { return mKeyStringsMapping.valueFor(s); }
+        const sp<AaptFile> getKeyStringsData() const { return mKeyStringsData; }
+        status_t setKeyStrings(const sp<AaptFile>& data);
+
+        status_t applyPublicTypeOrder();
+
+        const DefaultKeyedVector<String16, sp<Type> >& getTypes() const { return mTypes; }
+        const Vector<sp<Type> >& getOrderedTypes() const { return mOrderedTypes; }
+
+    private:
+        status_t setStrings(const sp<AaptFile>& data,
+                            ResStringPool* strings,
+                            DefaultKeyedVector<String16, uint32_t>* mappings);
+
+        const String16 mName;
+        const ssize_t mIncludedId;
+        DefaultKeyedVector<String16, sp<Type> > mTypes;
+        Vector<sp<Type> > mOrderedTypes;
+        sp<AaptFile> mTypeStringsData;
+        sp<AaptFile> mKeyStringsData;
+        ResStringPool mTypeStrings;
+        ResStringPool mKeyStrings;
+        DefaultKeyedVector<String16, uint32_t> mTypeStringsMapping;
+        DefaultKeyedVector<String16, uint32_t> mKeyStringsMapping;
+    };
+
+private:
+    void writePublicDefinitions(const String16& package, FILE* fp, bool pub);
+    sp<Package> getPackage(const String16& package);
+    sp<Type> getType(const String16& package,
+                     const String16& type,
+                     const SourcePos& pos,
+                     bool doSetIndex = false);
+    sp<Entry> getEntry(const String16& package,
+                       const String16& type,
+                       const String16& name,
+                       const SourcePos& pos,
+                       bool overlay,
+                       const ResTable_config* config = NULL,
+                       bool doSetIndex = false);
+    sp<const Entry> getEntry(uint32_t resID,
+                             const ResTable_config* config = NULL) const;
+    const Item* getItem(uint32_t resID, uint32_t attrID) const;
+    bool getItemValue(uint32_t resID, uint32_t attrID,
+                      Res_value* outValue);
+
+
+    String16 mAssetsPackage;
+    sp<AaptAssets> mAssets;
+    DefaultKeyedVector<String16, sp<Package> > mPackages;
+    Vector<sp<Package> > mOrderedPackages;
+    uint32_t mNextPackageId;
+    bool mHaveAppPackage;
+    bool mIsAppPackage;
+    size_t mNumLocal;
+    SourcePos mCurrentXmlPos;
+    Bundle* mBundle;
+    
+    // key = string resource name, value = set of locales in which that name is defined
+    map<String16, set<String8> > mLocalizations;
+};
+
+#endif
diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp
new file mode 100644
index 0000000..e2a921c
--- /dev/null
+++ b/tools/aapt/SourcePos.cpp
@@ -0,0 +1,171 @@
+#include "SourcePos.h"
+
+#include <stdarg.h>
+#include <vector>
+
+using namespace std;
+
+
+// ErrorPos
+// =============================================================================
+struct ErrorPos
+{
+    String8 file;
+    int line;
+    String8 error;
+    bool fatal;
+
+    ErrorPos();
+    ErrorPos(const ErrorPos& that);
+    ErrorPos(const String8& file, int line, const String8& error, bool fatal);
+    ~ErrorPos();
+    bool operator<(const ErrorPos& rhs) const;
+    bool operator==(const ErrorPos& rhs) const;
+    ErrorPos& operator=(const ErrorPos& rhs);
+
+    void print(FILE* to) const;
+};
+
+static vector<ErrorPos> g_errors;
+
+ErrorPos::ErrorPos()
+    :line(-1), fatal(false)
+{
+}
+
+ErrorPos::ErrorPos(const ErrorPos& that)
+    :file(that.file),
+     line(that.line),
+     error(that.error),
+     fatal(that.fatal)
+{
+}
+
+ErrorPos::ErrorPos(const String8& f, int l, const String8& e, bool fat)
+    :file(f),
+     line(l),
+     error(e),
+     fatal(fat)
+{
+}
+
+ErrorPos::~ErrorPos()
+{
+}
+
+bool
+ErrorPos::operator<(const ErrorPos& rhs) const
+{
+    if (this->file < rhs.file) return true;
+    if (this->file == rhs.file) {
+        if (this->line < rhs.line) return true;
+        if (this->line == rhs.line) {
+            if (this->error < rhs.error) return true;
+        }
+    }
+    return false;
+}
+
+bool
+ErrorPos::operator==(const ErrorPos& rhs) const
+{
+    return this->file == rhs.file
+            && this->line == rhs.line
+            && this->error == rhs.error;
+}
+
+ErrorPos&
+ErrorPos::operator=(const ErrorPos& rhs)
+{
+    this->file = rhs.file;
+    this->line = rhs.line;
+    this->error = rhs.error;
+    return *this;
+}
+
+void
+ErrorPos::print(FILE* to) const
+{
+    const char* type = fatal ? "error:" : "warning:";
+    
+    if (this->line >= 0) {
+        fprintf(to, "%s:%d: %s %s\n", this->file.string(), this->line, type, this->error.string());
+    } else {
+        fprintf(to, "%s: %s %s\n", this->file.string(), type, this->error.string());
+    }
+}
+
+// SourcePos
+// =============================================================================
+SourcePos::SourcePos(const String8& f, int l)
+    : file(f), line(l)
+{
+}
+
+SourcePos::SourcePos(const SourcePos& that)
+    : file(that.file), line(that.line)
+{
+}
+
+SourcePos::SourcePos()
+    : file("???", 0), line(-1)
+{
+}
+
+SourcePos::~SourcePos()
+{
+}
+
+int
+SourcePos::error(const char* fmt, ...) const
+{
+    int retval=0;
+    char buf[1024];
+    va_list ap;
+    va_start(ap, fmt);
+    retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+    va_end(ap);
+    char* p = buf + retval - 1;
+    while (p > buf && *p == '\n') {
+        *p = '\0';
+        p--;
+    }
+    g_errors.push_back(ErrorPos(this->file, this->line, String8(buf), true));
+    return retval;
+}
+
+int
+SourcePos::warning(const char* fmt, ...) const
+{
+    int retval=0;
+    char buf[1024];
+    va_list ap;
+    va_start(ap, fmt);
+    retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+    va_end(ap);
+    char* p = buf + retval - 1;
+    while (p > buf && *p == '\n') {
+        *p = '\0';
+        p--;
+    }
+    ErrorPos(this->file, this->line, String8(buf), false).print(stderr);
+    return retval;
+}
+
+bool
+SourcePos::hasErrors()
+{
+    return g_errors.size() > 0;
+}
+
+void
+SourcePos::printErrors(FILE* to)
+{
+    vector<ErrorPos>::const_iterator it;
+    for (it=g_errors.begin(); it!=g_errors.end(); it++) {
+        it->print(to);
+    }
+}
+
+
+
diff --git a/tools/aapt/SourcePos.h b/tools/aapt/SourcePos.h
new file mode 100644
index 0000000..33f72a9
--- /dev/null
+++ b/tools/aapt/SourcePos.h
@@ -0,0 +1,28 @@
+#ifndef SOURCEPOS_H
+#define SOURCEPOS_H
+
+#include <utils/String8.h>
+#include <stdio.h>
+
+using namespace android;
+
+class SourcePos
+{
+public:
+    String8 file;
+    int line;
+
+    SourcePos(const String8& f, int l);
+    SourcePos(const SourcePos& that);
+    SourcePos();
+    ~SourcePos();
+
+    int error(const char* fmt, ...) const;
+    int warning(const char* fmt, ...) const;
+
+    static bool hasErrors();
+    static void printErrors(FILE* to);
+};
+
+
+#endif // SOURCEPOS_H
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
new file mode 100644
index 0000000..158b391
--- /dev/null
+++ b/tools/aapt/StringPool.cpp
@@ -0,0 +1,574 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "StringPool.h"
+#include "ResourceTable.h"
+
+#include <utils/ByteOrder.h>
+#include <utils/SortedVector.h>
+#include "qsort_r_compat.h"
+
+#if HAVE_PRINTF_ZD
+#  define ZD "%zd"
+#  define ZD_TYPE ssize_t
+#else
+#  define ZD "%ld"
+#  define ZD_TYPE long
+#endif
+
+#define NOISY(x) //x
+
+void strcpy16_htod(uint16_t* dst, const uint16_t* src)
+{
+    while (*src) {
+        char16_t s = htods(*src);
+        *dst++ = s;
+        src++;
+    }
+    *dst = 0;
+}
+
+void printStringPool(const ResStringPool* pool)
+{
+    SortedVector<const void*> uniqueStrings;
+    const size_t N = pool->size();
+    for (size_t i=0; i<N; i++) {
+        size_t len;
+        if (pool->isUTF8()) {
+            uniqueStrings.add(pool->string8At(i, &len));
+        } else {
+            uniqueStrings.add(pool->stringAt(i, &len));
+        }
+    }
+
+    printf("String pool of " ZD " unique %s %s strings, " ZD " entries and "
+            ZD " styles using " ZD " bytes:\n",
+            (ZD_TYPE)uniqueStrings.size(), pool->isUTF8() ? "UTF-8" : "UTF-16",
+            pool->isSorted() ? "sorted" : "non-sorted",
+            (ZD_TYPE)N, (ZD_TYPE)pool->styleCount(), (ZD_TYPE)pool->bytes());
+
+    const size_t NS = pool->size();
+    for (size_t s=0; s<NS; s++) {
+        String8 str = pool->string8ObjectAt(s);
+        printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string());
+    }
+}
+
+String8 StringPool::entry::makeConfigsString() const {
+    String8 configStr(configTypeName);
+    if (configStr.size() > 0) configStr.append(" ");
+    if (configs.size() > 0) {
+        for (size_t j=0; j<configs.size(); j++) {
+            if (j > 0) configStr.append(", ");
+            configStr.append(configs[j].toString());
+        }
+    } else {
+        configStr = "(none)";
+    }
+    return configStr;
+}
+
+int StringPool::entry::compare(const entry& o) const {
+    // Strings with styles go first, to reduce the size of the styles array.
+    // We don't care about the relative order of these strings.
+    if (hasStyles) {
+        return o.hasStyles ? 0 : -1;
+    }
+    if (o.hasStyles) {
+        return 1;
+    }
+
+    // Sort unstyled strings by type, then by logical configuration.
+    int comp = configTypeName.compare(o.configTypeName);
+    if (comp != 0) {
+        return comp;
+    }
+    const size_t LHN = configs.size();
+    const size_t RHN = o.configs.size();
+    size_t i=0;
+    while (i < LHN && i < RHN) {
+        comp = configs[i].compareLogical(o.configs[i]);
+        if (comp != 0) {
+            return comp;
+        }
+        i++;
+    }
+    if (LHN < RHN) return -1;
+    else if (LHN > RHN) return 1;
+    return 0;
+}
+
+StringPool::StringPool(bool utf8) :
+        mUTF8(utf8), mValues(-1)
+{
+}
+
+ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans,
+        const String8* configTypeName, const ResTable_config* config)
+{
+    ssize_t res = add(value, false, configTypeName, config);
+    if (res >= 0) {
+        addStyleSpans(res, spans);
+    }
+    return res;
+}
+
+ssize_t StringPool::add(const String16& value,
+        bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config)
+{
+    ssize_t vidx = mValues.indexOfKey(value);
+    ssize_t pos = vidx >= 0 ? mValues.valueAt(vidx) : -1;
+    ssize_t eidx = pos >= 0 ? mEntryArray.itemAt(pos) : -1;
+    if (eidx < 0) {
+        eidx = mEntries.add(entry(value));
+        if (eidx < 0) {
+            fprintf(stderr, "Failure adding string %s\n", String8(value).string());
+            return eidx;
+        }
+    }
+
+    if (configTypeName != NULL) {
+        entry& ent = mEntries.editItemAt(eidx);
+        NOISY(printf("*** adding config type name %s, was %s\n",
+                configTypeName->string(), ent.configTypeName.string()));
+        if (ent.configTypeName.size() <= 0) {
+            ent.configTypeName = *configTypeName;
+        } else if (ent.configTypeName != *configTypeName) {
+            ent.configTypeName = " ";
+        }
+    }
+
+    if (config != NULL) {
+        // Add this to the set of configs associated with the string.
+        entry& ent = mEntries.editItemAt(eidx);
+        size_t addPos;
+        for (addPos=0; addPos<ent.configs.size(); addPos++) {
+            int cmp = ent.configs.itemAt(addPos).compareLogical(*config);
+            if (cmp >= 0) {
+                if (cmp > 0) {
+                    NOISY(printf("*** inserting config: %s\n", config->toString().string()));
+                    ent.configs.insertAt(*config, addPos);
+                }
+                break;
+            }
+        }
+        if (addPos >= ent.configs.size()) {
+            NOISY(printf("*** adding config: %s\n", config->toString().string()));
+            ent.configs.add(*config);
+        }
+    }
+
+    const bool first = vidx < 0;
+    const bool styled = (pos >= 0 && (size_t)pos < mEntryStyleArray.size()) ?
+        mEntryStyleArray[pos].spans.size() : 0;
+    if (first || styled || !mergeDuplicates) {
+        pos = mEntryArray.add(eidx);
+        if (first) {
+            vidx = mValues.add(value, pos);
+        }
+        entry& ent = mEntries.editItemAt(eidx);
+        ent.indices.add(pos);
+    }
+
+    NOISY(printf("Adding string %s to pool: pos=%d eidx=%d vidx=%d\n",
+            String8(value).string(), pos, eidx, vidx));
+    
+    return pos;
+}
+
+status_t StringPool::addStyleSpan(size_t idx, const String16& name,
+                                  uint32_t start, uint32_t end)
+{
+    entry_style_span span;
+    span.name = name;
+    span.span.firstChar = start;
+    span.span.lastChar = end;
+    return addStyleSpan(idx, span);
+}
+
+status_t StringPool::addStyleSpans(size_t idx, const Vector<entry_style_span>& spans)
+{
+    const size_t N=spans.size();
+    for (size_t i=0; i<N; i++) {
+        status_t err = addStyleSpan(idx, spans[i]);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+    return NO_ERROR;
+}
+
+status_t StringPool::addStyleSpan(size_t idx, const entry_style_span& span)
+{
+    // Place blank entries in the span array up to this index.
+    while (mEntryStyleArray.size() <= idx) {
+        mEntryStyleArray.add();
+    }
+
+    entry_style& style = mEntryStyleArray.editItemAt(idx);
+    style.spans.add(span);
+    mEntries.editItemAt(mEntryArray[idx]).hasStyles = true;
+    return NO_ERROR;
+}
+
+int StringPool::config_sort(void* state, const void* lhs, const void* rhs)
+{
+    StringPool* pool = (StringPool*)state;
+    const entry& lhe = pool->mEntries[pool->mEntryArray[*static_cast<const size_t*>(lhs)]];
+    const entry& rhe = pool->mEntries[pool->mEntryArray[*static_cast<const size_t*>(rhs)]];
+    return lhe.compare(rhe);
+}
+
+void StringPool::sortByConfig()
+{
+    LOG_ALWAYS_FATAL_IF(mOriginalPosToNewPos.size() > 0, "Can't sort string pool after already sorted.");
+
+    const size_t N = mEntryArray.size();
+
+    // This is a vector that starts out with a 1:1 mapping to entries
+    // in the array, which we will sort to come up with the desired order.
+    // At that point it maps from the new position in the array to the
+    // original position the entry appeared.
+    Vector<size_t> newPosToOriginalPos;
+    newPosToOriginalPos.setCapacity(N);
+    for (size_t i=0; i < N; i++) {
+        newPosToOriginalPos.add(i);
+    }
+
+    // Sort the array.
+    NOISY(printf("SORTING STRINGS BY CONFIGURATION...\n"));
+    // Vector::sort uses insertion sort, which is very slow for this data set.
+    // Use quicksort instead because we don't need a stable sort here.
+    qsort_r_compat(newPosToOriginalPos.editArray(), N, sizeof(size_t), this, config_sort);
+    //newPosToOriginalPos.sort(config_sort, this);
+    NOISY(printf("DONE SORTING STRINGS BY CONFIGURATION.\n"));
+
+    // Create the reverse mapping from the original position in the array
+    // to the new position where it appears in the sorted array.  This is
+    // so that clients can re-map any positions they had previously stored.
+    mOriginalPosToNewPos = newPosToOriginalPos;
+    for (size_t i=0; i<N; i++) {
+        mOriginalPosToNewPos.editItemAt(newPosToOriginalPos[i]) = i;
+    }
+
+#if 0
+    SortedVector<entry> entries;
+
+    for (size_t i=0; i<N; i++) {
+        printf("#%d was %d: %s\n", i, newPosToOriginalPos[i],
+                mEntries[mEntryArray[newPosToOriginalPos[i]]].makeConfigsString().string());
+        entries.add(mEntries[mEntryArray[i]]);
+    }
+
+    for (size_t i=0; i<entries.size(); i++) {
+        printf("Sorted config #%d: %s\n", i,
+                entries[i].makeConfigsString().string());
+    }
+#endif
+
+    // Now we rebuild the arrays.
+    Vector<entry> newEntries;
+    Vector<size_t> newEntryArray;
+    Vector<entry_style> newEntryStyleArray;
+    DefaultKeyedVector<size_t, size_t> origOffsetToNewOffset;
+
+    for (size_t i=0; i<N; i++) {
+        // We are filling in new offset 'i'; oldI is where we can find it
+        // in the original data structure.
+        size_t oldI = newPosToOriginalPos[i];
+        // This is the actual entry associated with the old offset.
+        const entry& oldEnt = mEntries[mEntryArray[oldI]];
+        // This is the same entry the last time we added it to the
+        // new entry array, if any.
+        ssize_t newIndexOfOffset = origOffsetToNewOffset.indexOfKey(oldI);
+        size_t newOffset;
+        if (newIndexOfOffset < 0) {
+            // This is the first time we have seen the entry, so add
+            // it.
+            newOffset = newEntries.add(oldEnt);
+            newEntries.editItemAt(newOffset).indices.clear();
+        } else {
+            // We have seen this entry before, use the existing one
+            // instead of adding it again.
+            newOffset = origOffsetToNewOffset.valueAt(newIndexOfOffset);
+        }
+        // Update the indices to include this new position.
+        newEntries.editItemAt(newOffset).indices.add(i);
+        // And add the offset of the entry to the new entry array.
+        newEntryArray.add(newOffset);
+        // Add any old style to the new style array.
+        if (mEntryStyleArray.size() > 0) {
+            if (oldI < mEntryStyleArray.size()) {
+                newEntryStyleArray.add(mEntryStyleArray[oldI]);
+            } else {
+                newEntryStyleArray.add(entry_style());
+            }
+        }
+    }
+
+    // Now trim any entries at the end of the new style array that are
+    // not needed.
+    for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) {
+        const entry_style& style = newEntryStyleArray[i];
+        if (style.spans.size() > 0) {
+            // That's it.
+            break;
+        }
+        // This one is not needed; remove.
+        newEntryStyleArray.removeAt(i);
+    }
+
+    // All done, install the new data structures and upate mValues with
+    // the new positions.
+    mEntries = newEntries;
+    mEntryArray = newEntryArray;
+    mEntryStyleArray = newEntryStyleArray;
+    mValues.clear();
+    for (size_t i=0; i<mEntries.size(); i++) {
+        const entry& ent = mEntries[i];
+        mValues.add(ent.value, ent.indices[0]);
+    }
+
+#if 0
+    printf("FINAL SORTED STRING CONFIGS:\n");
+    for (size_t i=0; i<mEntries.size(); i++) {
+        const entry& ent = mEntries[i];
+        printf("#" ZD " %s: %s\n", (ZD_TYPE)i, ent.makeConfigsString().string(),
+                String8(ent.value).string());
+    }
+#endif
+}
+
+sp<AaptFile> StringPool::createStringBlock()
+{
+    sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(),
+                                     String8());
+    status_t err = writeStringBlock(pool);
+    return err == NO_ERROR ? pool : NULL;
+}
+
+#define ENCODE_LENGTH(str, chrsz, strSize) \
+{ \
+    size_t maxMask = 1 << ((chrsz*8)-1); \
+    size_t maxSize = maxMask-1; \
+    if (strSize > maxSize) { \
+        *str++ = maxMask | ((strSize>>(chrsz*8))&maxSize); \
+    } \
+    *str++ = strSize; \
+}
+
+status_t StringPool::writeStringBlock(const sp<AaptFile>& pool)
+{
+    // Allow appending.  Sorry this is a little wacky.
+    if (pool->getSize() > 0) {
+        sp<AaptFile> block = createStringBlock();
+        if (block == NULL) {
+            return UNKNOWN_ERROR;
+        }
+        ssize_t res = pool->writeData(block->getData(), block->getSize());
+        return (res >= 0) ? (status_t)NO_ERROR : res;
+    }
+
+    // First we need to add all style span names to the string pool.
+    // We do this now (instead of when the span is added) so that these
+    // will appear at the end of the pool, not disrupting the order
+    // our client placed their own strings in it.
+    
+    const size_t STYLES = mEntryStyleArray.size();
+    size_t i;
+
+    for (i=0; i<STYLES; i++) {
+        entry_style& style = mEntryStyleArray.editItemAt(i);
+        const size_t N = style.spans.size();
+        for (size_t i=0; i<N; i++) {
+            entry_style_span& span = style.spans.editItemAt(i);
+            ssize_t idx = add(span.name, true);
+            if (idx < 0) {
+                fprintf(stderr, "Error adding span for style tag '%s'\n",
+                        String8(span.name).string());
+                return idx;
+            }
+            span.span.name.index = (uint32_t)idx;
+        }
+    }
+
+    const size_t ENTRIES = mEntryArray.size();
+
+    // Now build the pool of unique strings.
+
+    const size_t STRINGS = mEntries.size();
+    const size_t preSize = sizeof(ResStringPool_header)
+                         + (sizeof(uint32_t)*ENTRIES)
+                         + (sizeof(uint32_t)*STYLES);
+    if (pool->editData(preSize) == NULL) {
+        fprintf(stderr, "ERROR: Out of memory for string pool\n");
+        return NO_MEMORY;
+    }
+
+    const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t);
+
+    size_t strPos = 0;
+    for (i=0; i<STRINGS; i++) {
+        entry& ent = mEntries.editItemAt(i);
+        const size_t strSize = (ent.value.size());
+        const size_t lenSize = strSize > (size_t)(1<<((charSize*8)-1))-1 ?
+            charSize*2 : charSize;
+
+        String8 encStr;
+        if (mUTF8) {
+            encStr = String8(ent.value);
+        }
+
+        const size_t encSize = mUTF8 ? encStr.size() : 0;
+        const size_t encLenSize = mUTF8 ?
+            (encSize > (size_t)(1<<((charSize*8)-1))-1 ?
+                charSize*2 : charSize) : 0;
+
+        ent.offset = strPos;
+
+        const size_t totalSize = lenSize + encLenSize +
+            ((mUTF8 ? encSize : strSize)+1)*charSize;
+
+        void* dat = (void*)pool->editData(preSize + strPos + totalSize);
+        if (dat == NULL) {
+            fprintf(stderr, "ERROR: Out of memory for string pool\n");
+            return NO_MEMORY;
+        }
+        dat = (uint8_t*)dat + preSize + strPos;
+        if (mUTF8) {
+            uint8_t* strings = (uint8_t*)dat;
+
+            ENCODE_LENGTH(strings, sizeof(uint8_t), strSize)
+
+            ENCODE_LENGTH(strings, sizeof(uint8_t), encSize)
+
+            strncpy((char*)strings, encStr, encSize+1);
+        } else {
+            uint16_t* strings = (uint16_t*)dat;
+
+            ENCODE_LENGTH(strings, sizeof(uint16_t), strSize)
+
+            strcpy16_htod(strings, ent.value);
+        }
+
+        strPos += totalSize;
+    }
+
+    // Pad ending string position up to a uint32_t boundary.
+
+    if (strPos&0x3) {
+        size_t padPos = ((strPos+3)&~0x3);
+        uint8_t* dat = (uint8_t*)pool->editData(preSize + padPos);
+        if (dat == NULL) {
+            fprintf(stderr, "ERROR: Out of memory padding string pool\n");
+            return NO_MEMORY;
+        }
+        memset(dat+preSize+strPos, 0, padPos-strPos);
+        strPos = padPos;
+    }
+
+    // Build the pool of style spans.
+
+    size_t styPos = strPos;
+    for (i=0; i<STYLES; i++) {
+        entry_style& ent = mEntryStyleArray.editItemAt(i);
+        const size_t N = ent.spans.size();
+        const size_t totalSize = (N*sizeof(ResStringPool_span))
+                               + sizeof(ResStringPool_ref);
+
+        ent.offset = styPos-strPos;
+        uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + totalSize);
+        if (dat == NULL) {
+            fprintf(stderr, "ERROR: Out of memory for string styles\n");
+            return NO_MEMORY;
+        }
+        ResStringPool_span* span = (ResStringPool_span*)(dat+preSize+styPos);
+        for (size_t i=0; i<N; i++) {
+            span->name.index = htodl(ent.spans[i].span.name.index);
+            span->firstChar = htodl(ent.spans[i].span.firstChar);
+            span->lastChar = htodl(ent.spans[i].span.lastChar);
+            span++;
+        }
+        span->name.index = htodl(ResStringPool_span::END);
+
+        styPos += totalSize;
+    }
+
+    if (STYLES > 0) {
+        // Add full terminator at the end (when reading we validate that
+        // the end of the pool is fully terminated to simplify error
+        // checking).
+        size_t extra = sizeof(ResStringPool_span)-sizeof(ResStringPool_ref);
+        uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + extra);
+        if (dat == NULL) {
+            fprintf(stderr, "ERROR: Out of memory for string styles\n");
+            return NO_MEMORY;
+        }
+        uint32_t* p = (uint32_t*)(dat+preSize+styPos);
+        while (extra > 0) {
+            *p++ = htodl(ResStringPool_span::END);
+            extra -= sizeof(uint32_t);
+        }
+        styPos += extra;
+    }
+
+    // Write header.
+
+    ResStringPool_header* header =
+        (ResStringPool_header*)pool->padData(sizeof(uint32_t));
+    if (header == NULL) {
+        fprintf(stderr, "ERROR: Out of memory for string pool\n");
+        return NO_MEMORY;
+    }
+    memset(header, 0, sizeof(*header));
+    header->header.type = htods(RES_STRING_POOL_TYPE);
+    header->header.headerSize = htods(sizeof(*header));
+    header->header.size = htodl(pool->getSize());
+    header->stringCount = htodl(ENTRIES);
+    header->styleCount = htodl(STYLES);
+    if (mUTF8) {
+        header->flags |= htodl(ResStringPool_header::UTF8_FLAG);
+    }
+    header->stringsStart = htodl(preSize);
+    header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0);
+
+    // Write string index array.
+
+    uint32_t* index = (uint32_t*)(header+1);
+    for (i=0; i<ENTRIES; i++) {
+        entry& ent = mEntries.editItemAt(mEntryArray[i]);
+        *index++ = htodl(ent.offset);
+        NOISY(printf("Writing entry #%d: \"%s\" ent=%d off=%d\n", i,
+                String8(ent.value).string(),
+                mEntryArray[i], ent.offset));
+    }
+
+    // Write style index array.
+
+    for (i=0; i<STYLES; i++) {
+        *index++ = htodl(mEntryStyleArray[i].offset);
+    }
+
+    return NO_ERROR;
+}
+
+ssize_t StringPool::offsetForString(const String16& val) const
+{
+    const Vector<size_t>* indices = offsetsForString(val);
+    ssize_t res = indices != NULL && indices->size() > 0 ? indices->itemAt(0) : -1;
+    NOISY(printf("Offset for string %s: %d (%s)\n", String8(val).string(), res,
+            res >= 0 ? String8(mEntries[mEntryArray[res]].value).string() : String8()));
+    return res;
+}
+
+const Vector<size_t>* StringPool::offsetsForString(const String16& val) const
+{
+    ssize_t pos = mValues.valueFor(val);
+    if (pos < 0) {
+        return NULL;
+    }
+    return &mEntries[mEntryArray[pos]].indices;
+}
diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h
new file mode 100644
index 0000000..1b3abfd
--- /dev/null
+++ b/tools/aapt/StringPool.h
@@ -0,0 +1,183 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef STRING_POOL_H
+#define STRING_POOL_H
+
+#include "Main.h"
+#include "AaptAssets.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/String16.h>
+#include <utils/TypeHelpers.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <libexpat/expat.h>
+
+using namespace android;
+
+#define PRINT_STRING_METRICS 0
+
+void strcpy16_htod(uint16_t* dst, const uint16_t* src);
+
+void printStringPool(const ResStringPool* pool);
+
+/**
+ * The StringPool class is used as an intermediate representation for
+ * generating the string pool resource data structure that can be parsed with
+ * ResStringPool in include/utils/ResourceTypes.h.
+ */
+class StringPool
+{
+public:
+    struct entry {
+        entry() : offset(0) { }
+        entry(const String16& _value) : value(_value), offset(0), hasStyles(false) { }
+        entry(const entry& o) : value(o.value), offset(o.offset),
+                hasStyles(o.hasStyles), indices(o.indices),
+                configTypeName(o.configTypeName), configs(o.configs) { }
+
+        String16 value;
+        size_t offset;
+        bool hasStyles;
+        Vector<size_t> indices;
+        String8 configTypeName;
+        Vector<ResTable_config> configs;
+
+        String8 makeConfigsString() const;
+
+        int compare(const entry& o) const;
+
+        inline bool operator<(const entry& o) const { return compare(o) < 0; }
+        inline bool operator<=(const entry& o) const { return compare(o) <= 0; }
+        inline bool operator==(const entry& o) const { return compare(o) == 0; }
+        inline bool operator!=(const entry& o) const { return compare(o) != 0; }
+        inline bool operator>=(const entry& o) const { return compare(o) >= 0; }
+        inline bool operator>(const entry& o) const { return compare(o) > 0; }
+    };
+
+    struct entry_style_span {
+        String16 name;
+        ResStringPool_span span;
+    };
+
+    struct entry_style {
+        entry_style() : offset(0) { }
+
+        entry_style(const entry_style& o) : offset(o.offset), spans(o.spans) { }
+
+        size_t offset;
+        Vector<entry_style_span> spans;
+    };
+
+    /**
+     * If 'utf8' is true, strings will be encoded with UTF-8 instead of
+     * left in Java's native UTF-16.
+     */
+    explicit StringPool(bool utf8 = false);
+
+    /**
+     * Add a new string to the pool.  If mergeDuplicates is true, thenif
+     * the string already exists the existing entry for it will be used;
+     * otherwise, or if the value doesn't already exist, a new entry is
+     * created.
+     *
+     * Returns the index in the entry array of the new string entry.
+     */
+    ssize_t add(const String16& value, bool mergeDuplicates = false,
+            const String8* configTypeName = NULL, const ResTable_config* config = NULL);
+
+    ssize_t add(const String16& value, const Vector<entry_style_span>& spans,
+            const String8* configTypeName = NULL, const ResTable_config* config = NULL);
+
+    status_t addStyleSpan(size_t idx, const String16& name,
+                          uint32_t start, uint32_t end);
+    status_t addStyleSpans(size_t idx, const Vector<entry_style_span>& spans);
+    status_t addStyleSpan(size_t idx, const entry_style_span& span);
+
+    // Sort the contents of the string block by the configuration associated
+    // with each item.  After doing this you can use mapOriginalPosToNewPos()
+    // to find out the new position given the position originally returned by
+    // add().
+    void sortByConfig();
+
+    // For use after sortByConfig() to map from the original position of
+    // a string to its new sorted position.
+    size_t mapOriginalPosToNewPos(size_t originalPos) const {
+        return mOriginalPosToNewPos.itemAt(originalPos);
+    }
+
+    sp<AaptFile> createStringBlock();
+
+    status_t writeStringBlock(const sp<AaptFile>& pool);
+
+    /**
+     * Find out an offset in the pool for a particular string.  If the string
+     * pool is sorted, this can not be called until after createStringBlock()
+     * or writeStringBlock() has been called
+     * (which determines the offsets).  In the case of a string that appears
+     * multiple times in the pool, the first offset will be returned.  Returns
+     * -1 if the string does not exist.
+     */
+    ssize_t offsetForString(const String16& val) const;
+
+    /**
+     * Find all of the offsets in the pool for a particular string.  If the
+     * string pool is sorted, this can not be called until after
+     * createStringBlock() or writeStringBlock() has been called
+     * (which determines the offsets).  Returns NULL if the string does not exist.
+     */
+    const Vector<size_t>* offsetsForString(const String16& val) const;
+
+private:
+    static int config_sort(void* state, const void* lhs, const void* rhs);
+
+    const bool                              mUTF8;
+
+    // The following data structures represent the actual structures
+    // that will be generated for the final string pool.
+
+    // Raw array of unique strings, in some arbitrary order.  This is the
+    // actual strings that appear in the final string pool, in the order
+    // that they will be written.
+    Vector<entry>                           mEntries;
+    // Array of indices into mEntries, in the order they were
+    // added to the pool.  This can be different than mEntries
+    // if the same string was added multiple times (it will appear
+    // once in mEntries, with multiple occurrences in this array).
+    // This is the lookup array that will be written for finding
+    // the string for each offset/position in the string pool.
+    Vector<size_t>                          mEntryArray;
+    // Optional style span information associated with each index of
+    // mEntryArray.
+    Vector<entry_style>                     mEntryStyleArray;
+
+    // The following data structures are used for book-keeping as the
+    // string pool is constructed.
+
+    // Unique set of all the strings added to the pool, mapped to
+    // the first index of mEntryArray where the value was added.
+    DefaultKeyedVector<String16, ssize_t>   mValues;
+    // This array maps from the original position a string was placed at
+    // in mEntryArray to its new position after being sorted with sortByConfig().
+    Vector<size_t>                          mOriginalPosToNewPos;
+};
+
+// The entry types are trivially movable because all fields they contain, including
+// the vectors and strings, are trivially movable.
+namespace android {
+    ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry);
+    ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry_style_span);
+    ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry_style);
+};
+
+#endif
+
diff --git a/tools/aapt/WorkQueue.cpp b/tools/aapt/WorkQueue.cpp
new file mode 100644
index 0000000..24a962f
--- /dev/null
+++ b/tools/aapt/WorkQueue.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "WorkQueue"
+
+#include <utils/Log.h>
+#include "WorkQueue.h"
+
+namespace android {
+
+// --- WorkQueue ---
+
+WorkQueue::WorkQueue(size_t maxThreads, bool canCallJava) :
+        mMaxThreads(maxThreads), mCanCallJava(canCallJava),
+        mCanceled(false), mFinished(false), mIdleThreads(0) {
+}
+
+WorkQueue::~WorkQueue() {
+    if (!cancel()) {
+        finish();
+    }
+}
+
+status_t WorkQueue::schedule(WorkUnit* workUnit, size_t backlog) {
+    AutoMutex _l(mLock);
+
+    if (mFinished || mCanceled) {
+        return INVALID_OPERATION;
+    }
+
+    if (mWorkThreads.size() < mMaxThreads
+            && mIdleThreads < mWorkUnits.size() + 1) {
+        sp<WorkThread> workThread = new WorkThread(this, mCanCallJava);
+        status_t status = workThread->run("WorkQueue::WorkThread");
+        if (status) {
+            return status;
+        }
+        mWorkThreads.add(workThread);
+        mIdleThreads += 1;
+    } else if (backlog) {
+        while (mWorkUnits.size() >= mMaxThreads * backlog) {
+            mWorkDequeuedCondition.wait(mLock);
+            if (mFinished || mCanceled) {
+                return INVALID_OPERATION;
+            }
+        }
+    }
+
+    mWorkUnits.add(workUnit);
+    mWorkChangedCondition.broadcast();
+    return OK;
+}
+
+status_t WorkQueue::cancel() {
+    AutoMutex _l(mLock);
+
+    return cancelLocked();
+}
+
+status_t WorkQueue::cancelLocked() {
+    if (mFinished) {
+        return INVALID_OPERATION;
+    }
+
+    if (!mCanceled) {
+        mCanceled = true;
+
+        size_t count = mWorkUnits.size();
+        for (size_t i = 0; i < count; i++) {
+            delete mWorkUnits.itemAt(i);
+        }
+        mWorkUnits.clear();
+        mWorkChangedCondition.broadcast();
+        mWorkDequeuedCondition.broadcast();
+    }
+    return OK;
+}
+
+status_t WorkQueue::finish() {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mFinished) {
+            return INVALID_OPERATION;
+        }
+
+        mFinished = true;
+        mWorkChangedCondition.broadcast();
+    } // release lock
+
+    // It is not possible for the list of work threads to change once the mFinished
+    // flag has been set, so we can access mWorkThreads outside of the lock here.
+    size_t count = mWorkThreads.size();
+    for (size_t i = 0; i < count; i++) {
+        mWorkThreads.itemAt(i)->join();
+    }
+    mWorkThreads.clear();
+    return OK;
+}
+
+bool WorkQueue::threadLoop() {
+    WorkUnit* workUnit;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        for (;;) {
+            if (mCanceled) {
+                return false;
+            }
+
+            if (!mWorkUnits.isEmpty()) {
+                workUnit = mWorkUnits.itemAt(0);
+                mWorkUnits.removeAt(0);
+                mIdleThreads -= 1;
+                mWorkDequeuedCondition.broadcast();
+                break;
+            }
+
+            if (mFinished) {
+                return false;
+            }
+
+            mWorkChangedCondition.wait(mLock);
+        }
+    } // release lock
+
+    bool shouldContinue = workUnit->run();
+    delete workUnit;
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        mIdleThreads += 1;
+
+        if (!shouldContinue) {
+            cancelLocked();
+            return false;
+        }
+    } // release lock
+
+    return true;
+}
+
+// --- WorkQueue::WorkThread ---
+
+WorkQueue::WorkThread::WorkThread(WorkQueue* workQueue, bool canCallJava) :
+        Thread(canCallJava), mWorkQueue(workQueue) {
+}
+
+WorkQueue::WorkThread::~WorkThread() {
+}
+
+bool WorkQueue::WorkThread::threadLoop() {
+    return mWorkQueue->threadLoop();
+}
+
+};  // namespace android
diff --git a/tools/aapt/WorkQueue.h b/tools/aapt/WorkQueue.h
new file mode 100644
index 0000000..d38f05d
--- /dev/null
+++ b/tools/aapt/WorkQueue.h
@@ -0,0 +1,119 @@
+/*]
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_WORK_QUEUE_H
+#define AAPT_WORK_QUEUE_H
+
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+namespace android {
+
+/*
+ * A threaded work queue.
+ *
+ * This class is designed to make it easy to run a bunch of isolated work
+ * units in parallel, using up to the specified number of threads.
+ * To use it, write a loop to post work units to the work queue, then synchronize
+ * on the queue at the end.
+ */
+class WorkQueue {
+public:
+    class WorkUnit {
+    public:
+        WorkUnit() { }
+        virtual ~WorkUnit() { }
+
+        /*
+         * Runs the work unit.
+         * If the result is 'true' then the work queue continues scheduling work as usual.
+         * If the result is 'false' then the work queue is canceled.
+         */
+        virtual bool run() = 0;
+    };
+
+    /* Creates a work queue with the specified maximum number of work threads. */
+    WorkQueue(size_t maxThreads, bool canCallJava = true);
+
+    /* Destroys the work queue.
+     * Cancels pending work and waits for all remaining threads to complete.
+     */
+    ~WorkQueue();
+
+    /* Posts a work unit to run later.
+     * If the work queue has been canceled or is already finished, returns INVALID_OPERATION
+     * and does not take ownership of the work unit (caller must destroy it itself).
+     * Otherwise, returns OK and takes ownership of the work unit (the work queue will
+     * destroy it automatically).
+     *
+     * For flow control, this method blocks when the size of the pending work queue is more
+     * 'backlog' times the number of threads.  This condition reduces the rate of entry into
+     * the pending work queue and prevents it from growing much more rapidly than the
+     * work threads can actually handle.
+     *
+     * If 'backlog' is 0, then no throttle is applied.
+     */
+    status_t schedule(WorkUnit* workUnit, size_t backlog = 2);
+
+    /* Cancels all pending work.
+     * If the work queue is already finished, returns INVALID_OPERATION.
+     * If the work queue is already canceled, returns OK and does nothing else.
+     * Otherwise, returns OK, discards all pending work units and prevents additional
+     * work units from being scheduled.
+     *
+     * Call finish() after cancel() to wait for all remaining work to complete.
+     */
+    status_t cancel();
+
+    /* Waits for all work to complete.
+     * If the work queue is already finished, returns INVALID_OPERATION.
+     * Otherwise, waits for all work to complete and returns OK.
+     */
+    status_t finish();
+
+private:
+    class WorkThread : public Thread {
+    public:
+        WorkThread(WorkQueue* workQueue, bool canCallJava);
+        virtual ~WorkThread();
+
+    private:
+        virtual bool threadLoop();
+
+        WorkQueue* const mWorkQueue;
+    };
+
+    status_t cancelLocked();
+    bool threadLoop(); // called from each work thread
+
+    const size_t mMaxThreads;
+    const bool mCanCallJava;
+
+    Mutex mLock;
+    Condition mWorkChangedCondition;
+    Condition mWorkDequeuedCondition;
+
+    bool mCanceled;
+    bool mFinished;
+    size_t mIdleThreads;
+    Vector<sp<WorkThread> > mWorkThreads;
+    Vector<WorkUnit*> mWorkUnits;
+};
+
+}; // namespace android
+
+#endif // AAPT_WORK_QUEUE_H
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
new file mode 100644
index 0000000..a663ad5
--- /dev/null
+++ b/tools/aapt/XMLNode.cpp
@@ -0,0 +1,1510 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "XMLNode.h"
+#include "ResourceTable.h"
+#include "pseudolocalize.h"
+
+#include <utils/ByteOrder.h>
+#include <errno.h>
+#include <string.h>
+
+#ifndef HAVE_MS_C_RUNTIME
+#define O_BINARY 0
+#endif
+
+#define NOISY(x) //x
+#define NOISY_PARSE(x) //x
+
+const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/";
+const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
+const char* const RESOURCES_AUTO_PACKAGE_NAMESPACE = "http://schemas.android.com/apk/res-auto";
+const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/";
+
+const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
+const char* const ALLOWED_XLIFF_ELEMENTS[] = {
+        "bpt",
+        "ept",
+        "it",
+        "ph",
+        "g",
+        "bx",
+        "ex",
+        "x"
+    };
+
+bool isWhitespace(const char16_t* str)
+{
+    while (*str != 0 && *str < 128 && isspace(*str)) {
+        str++;
+    }
+    return *str == 0;
+}
+
+static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE);
+static const String16 RESOURCES_PREFIX_AUTO_PACKAGE(RESOURCES_AUTO_PACKAGE_NAMESPACE);
+static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE);
+static const String16 RESOURCES_TOOLS_NAMESPACE("http://schemas.android.com/tools");
+
+String16 getNamespaceResourcePackage(String16 appPackage, String16 namespaceUri, bool* outIsPublic)
+{
+    //printf("%s starts with %s?\n", String8(namespaceUri).string(),
+    //       String8(RESOURCES_PREFIX).string());
+    size_t prefixSize;
+    bool isPublic = true;
+    if(namespaceUri.startsWith(RESOURCES_PREFIX_AUTO_PACKAGE)) {
+        NOISY(printf("Using default application package: %s -> %s\n", String8(namespaceUri).string(), String8(appPackage).string()));
+        isPublic = true;
+        return appPackage;
+    } else if (namespaceUri.startsWith(RESOURCES_PREFIX)) {
+        prefixSize = RESOURCES_PREFIX.size();
+    } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) {
+        isPublic = false;
+        prefixSize = RESOURCES_PRV_PREFIX.size();
+    } else {
+        if (outIsPublic) *outIsPublic = isPublic; // = true
+        return String16();
+    }
+
+    //printf("YES!\n");
+    //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string());
+    if (outIsPublic) *outIsPublic = isPublic;
+    return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize);
+}
+
+status_t hasSubstitutionErrors(const char* fileName,
+                               ResXMLTree* inXml,
+                               String16 str16)
+{
+    const char16_t* str = str16.string();
+    const char16_t* p = str;
+    const char16_t* end = str + str16.size();
+
+    bool nonpositional = false;
+    int argCount = 0;
+
+    while (p < end) {
+        /*
+         * Look for the start of a Java-style substitution sequence.
+         */
+        if (*p == '%' && p + 1 < end) {
+            p++;
+
+            // A literal percent sign represented by %%
+            if (*p == '%') {
+                p++;
+                continue;
+            }
+
+            argCount++;
+
+            if (*p >= '0' && *p <= '9') {
+                do {
+                    p++;
+                } while (*p >= '0' && *p <= '9');
+                if (*p != '$') {
+                    // This must be a size specification instead of position.
+                    nonpositional = true;
+                }
+            } else if (*p == '<') {
+                // Reusing last argument; bad idea since it can be re-arranged.
+                nonpositional = true;
+                p++;
+
+                // Optionally '$' can be specified at the end.
+                if (p < end && *p == '$') {
+                    p++;
+                }
+            } else {
+                nonpositional = true;
+            }
+
+            // Ignore flags and widths
+            while (p < end && (*p == '-' ||
+                    *p == '#' ||
+                    *p == '+' ||
+                    *p == ' ' ||
+                    *p == ',' ||
+                    *p == '(' ||
+                    (*p >= '0' && *p <= '9'))) {
+                p++;
+            }
+
+            /*
+             * This is a shortcut to detect strings that are going to Time.format()
+             * instead of String.format()
+             *
+             * Comparison of String.format() and Time.format() args:
+             *
+             * String: ABC E GH  ST X abcdefgh  nost x
+             *   Time:    DEFGHKMS W Za  d   hkm  s w yz
+             *
+             * Therefore we know it's definitely Time if we have:
+             *     DFKMWZkmwyz
+             */
+            if (p < end) {
+                switch (*p) {
+                case 'D':
+                case 'F':
+                case 'K':
+                case 'M':
+                case 'W':
+                case 'Z':
+                case 'k':
+                case 'm':
+                case 'w':
+                case 'y':
+                case 'z':
+                    return NO_ERROR;
+                }
+            }
+        }
+
+        p++;
+    }
+
+    /*
+     * If we have more than one substitution in this string and any of them
+     * are not in positional form, give the user an error.
+     */
+    if (argCount > 1 && nonpositional) {
+        SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                "Multiple substitutions specified in non-positional format; "
+                "did you mean to add the formatted=\"false\" attribute?\n");
+        return NOT_ENOUGH_DATA;
+    }
+
+    return NO_ERROR;
+}
+
+status_t parseStyledString(Bundle* bundle,
+                           const char* fileName,
+                           ResXMLTree* inXml,
+                           const String16& endTag,
+                           String16* outString,
+                           Vector<StringPool::entry_style_span>* outSpans,
+                           bool isFormatted,
+                           bool pseudolocalize)
+{
+    Vector<StringPool::entry_style_span> spanStack;
+    String16 curString;
+    String16 rawString;
+    const char* errorMsg;
+    int xliffDepth = 0;
+    bool firstTime = true;
+
+    size_t len;
+    ResXMLTree::event_code_t code;
+    while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+
+        if (code == ResXMLTree::TEXT) {
+            String16 text(inXml->getText(&len));
+            if (firstTime && text.size() > 0) {
+                firstTime = false;
+                if (text.string()[0] == '@') {
+                    // If this is a resource reference, don't do the pseudoloc.
+                    pseudolocalize = false;
+                }
+            }
+            if (xliffDepth == 0 && pseudolocalize) {
+                std::string orig(String8(text).string());
+                std::string pseudo = pseudolocalize_string(orig);
+                curString.append(String16(String8(pseudo.c_str())));
+            } else {
+                if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) {
+                    return UNKNOWN_ERROR;
+                } else {
+                    curString.append(text);
+                }
+            }
+        } else if (code == ResXMLTree::START_TAG) {
+            const String16 element16(inXml->getElementName(&len));
+            const String8 element8(element16);
+
+            size_t nslen;
+            const uint16_t* ns = inXml->getElementNamespace(&nslen);
+            if (ns == NULL) {
+                ns = (const uint16_t*)"\0\0";
+                nslen = 0;
+            }
+            const String8 nspace(String16(ns, nslen));
+            if (nspace == XLIFF_XMLNS) {
+                const int N = sizeof(ALLOWED_XLIFF_ELEMENTS)/sizeof(ALLOWED_XLIFF_ELEMENTS[0]);
+                for (int i=0; i<N; i++) {
+                    if (element8 == ALLOWED_XLIFF_ELEMENTS[i]) {
+                        xliffDepth++;
+                        // in this case, treat it like it was just text, in other words, do nothing
+                        // here and silently drop this element
+                        goto moveon;
+                    }
+                }
+                {
+                    SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                            "Found unsupported XLIFF tag <%s>\n",
+                            element8.string());
+                    return UNKNOWN_ERROR;
+                }
+moveon:
+                continue;
+            }
+
+            if (outSpans == NULL) {
+                SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                        "Found style tag <%s> where styles are not allowed\n", element8.string());
+                return UNKNOWN_ERROR;
+            }
+
+            if (!ResTable::collectString(outString, curString.string(),
+                                         curString.size(), false, &errorMsg, true)) {
+                SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
+                        errorMsg, String8(curString).string());
+                return UNKNOWN_ERROR;
+            }
+            rawString.append(curString);
+            curString = String16();
+
+            StringPool::entry_style_span span;
+            span.name = element16;
+            for (size_t ai=0; ai<inXml->getAttributeCount(); ai++) {
+                span.name.append(String16(";"));
+                const char16_t* str = inXml->getAttributeName(ai, &len);
+                span.name.append(str, len);
+                span.name.append(String16("="));
+                str = inXml->getAttributeStringValue(ai, &len);
+                span.name.append(str, len);
+            }
+            //printf("Span: %s\n", String8(span.name).string());
+            span.span.firstChar = span.span.lastChar = outString->size();
+            spanStack.push(span);
+
+        } else if (code == ResXMLTree::END_TAG) {
+            size_t nslen;
+            const uint16_t* ns = inXml->getElementNamespace(&nslen);
+            if (ns == NULL) {
+                ns = (const uint16_t*)"\0\0";
+                nslen = 0;
+            }
+            const String8 nspace(String16(ns, nslen));
+            if (nspace == XLIFF_XMLNS) {
+                xliffDepth--;
+                continue;
+            }
+            if (!ResTable::collectString(outString, curString.string(),
+                                         curString.size(), false, &errorMsg, true)) {
+                SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
+                        errorMsg, String8(curString).string());
+                return UNKNOWN_ERROR;
+            }
+            rawString.append(curString);
+            curString = String16();
+
+            if (spanStack.size() == 0) {
+                if (strcmp16(inXml->getElementName(&len), endTag.string()) != 0) {
+                    SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                            "Found tag %s where <%s> close is expected\n",
+                            String8(inXml->getElementName(&len)).string(),
+                            String8(endTag).string());
+                    return UNKNOWN_ERROR;
+                }
+                break;
+            }
+            StringPool::entry_style_span span = spanStack.top();
+            String16 spanTag;
+            ssize_t semi = span.name.findFirst(';');
+            if (semi >= 0) {
+                spanTag.setTo(span.name.string(), semi);
+            } else {
+                spanTag.setTo(span.name);
+            }
+            if (strcmp16(inXml->getElementName(&len), spanTag.string()) != 0) {
+                SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                        "Found close tag %s where close tag %s is expected\n",
+                        String8(inXml->getElementName(&len)).string(),
+                        String8(spanTag).string());
+                return UNKNOWN_ERROR;
+            }
+            bool empty = true;
+            if (outString->size() > 0) {
+                span.span.lastChar = outString->size()-1;
+                if (span.span.lastChar >= span.span.firstChar) {
+                    empty = false;
+                    outSpans->add(span);
+                }
+            }
+            spanStack.pop();
+
+            /*
+             * This warning seems to be just an irritation to most people,
+             * since it is typically introduced by translators who then never
+             * see the warning.
+             */
+            if (0 && empty) {
+                fprintf(stderr, "%s:%d: warning: empty '%s' span found in text '%s'\n",
+                        fileName, inXml->getLineNumber(),
+                        String8(spanTag).string(), String8(*outString).string());
+
+            }
+        } else if (code == ResXMLTree::START_NAMESPACE) {
+            // nothing
+        }
+    }
+
+    if (code == ResXMLTree::BAD_DOCUMENT) {
+            SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                    "Error parsing XML\n");
+    }
+
+    if (outSpans != NULL && outSpans->size() > 0) {
+        if (curString.size() > 0) {
+            if (!ResTable::collectString(outString, curString.string(),
+                                         curString.size(), false, &errorMsg, true)) {
+                SourcePos(String8(fileName), inXml->getLineNumber()).error(
+                        "%s (in %s)\n",
+                        errorMsg, String8(curString).string());
+                return UNKNOWN_ERROR;
+            }
+        }
+    } else {
+        // There is no style information, so string processing will happen
+        // later as part of the overall type conversion.  Return to the
+        // client the raw unprocessed text.
+        rawString.append(curString);
+        outString->setTo(rawString);
+    }
+
+    return NO_ERROR;
+}
+
+struct namespace_entry {
+    String8 prefix;
+    String8 uri;
+};
+
+static String8 make_prefix(int depth)
+{
+    String8 prefix;
+    int i;
+    for (i=0; i<depth; i++) {
+        prefix.append("  ");
+    }
+    return prefix;
+}
+
+static String8 build_namespace(const Vector<namespace_entry>& namespaces,
+        const uint16_t* ns)
+{
+    String8 str;
+    if (ns != NULL) {
+        str = String8(ns);
+        const size_t N = namespaces.size();
+        for (size_t i=0; i<N; i++) {
+            const namespace_entry& ne = namespaces.itemAt(i);
+            if (ne.uri == str) {
+                str = ne.prefix;
+                break;
+            }
+        }
+        str.append(":");
+    }
+    return str;
+}
+
+void printXMLBlock(ResXMLTree* block)
+{
+    block->restart();
+
+    Vector<namespace_entry> namespaces;
+    
+    ResXMLTree::event_code_t code;
+    int depth = 0;
+    while ((code=block->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        String8 prefix = make_prefix(depth);
+        int i;
+        if (code == ResXMLTree::START_TAG) {
+            size_t len;
+            const uint16_t* ns16 = block->getElementNamespace(&len);
+            String8 elemNs = build_namespace(namespaces, ns16);
+            const uint16_t* com16 = block->getComment(&len);
+            if (com16) {
+                printf("%s <!-- %s -->\n", prefix.string(), String8(com16).string());
+            }
+            printf("%sE: %s%s (line=%d)\n", prefix.string(), elemNs.string(),
+                   String8(block->getElementName(&len)).string(),
+                   block->getLineNumber());
+            int N = block->getAttributeCount();
+            depth++;
+            prefix = make_prefix(depth);
+            for (i=0; i<N; i++) {
+                uint32_t res = block->getAttributeNameResID(i);
+                ns16 = block->getAttributeNamespace(i, &len);
+                String8 ns = build_namespace(namespaces, ns16);
+                String8 name(block->getAttributeName(i, &len));
+                printf("%sA: ", prefix.string());
+                if (res) {
+                    printf("%s%s(0x%08x)", ns.string(), name.string(), res);
+                } else {
+                    printf("%s%s", ns.string(), name.string());
+                }
+                Res_value value;
+                block->getAttributeValue(i, &value);
+                if (value.dataType == Res_value::TYPE_NULL) {
+                    printf("=(null)");
+                } else if (value.dataType == Res_value::TYPE_REFERENCE) {
+                    printf("=@0x%x", (int)value.data);
+                } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
+                    printf("=?0x%x", (int)value.data);
+                } else if (value.dataType == Res_value::TYPE_STRING) {
+                    printf("=\"%s\"",
+                            ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i,
+                                        &len)).string()).string());
+                } else {
+                    printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data);
+                }
+                const char16_t* val = block->getAttributeStringValue(i, &len);
+                if (val != NULL) {
+                    printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val).string()).
+                            string());
+                }
+                printf("\n");
+            }
+        } else if (code == ResXMLTree::END_TAG) {
+            depth--;
+        } else if (code == ResXMLTree::START_NAMESPACE) {
+            namespace_entry ns;
+            size_t len;
+            const uint16_t* prefix16 = block->getNamespacePrefix(&len);
+            if (prefix16) {
+                ns.prefix = String8(prefix16);
+            } else {
+                ns.prefix = "<DEF>";
+            }
+            ns.uri = String8(block->getNamespaceUri(&len));
+            namespaces.push(ns);
+            printf("%sN: %s=%s\n", prefix.string(), ns.prefix.string(),
+                    ns.uri.string());
+            depth++;
+        } else if (code == ResXMLTree::END_NAMESPACE) {
+            depth--;
+            const namespace_entry& ns = namespaces.top();
+            size_t len;
+            const uint16_t* prefix16 = block->getNamespacePrefix(&len);
+            String8 pr;
+            if (prefix16) {
+                pr = String8(prefix16);
+            } else {
+                pr = "<DEF>";
+            }
+            if (ns.prefix != pr) {
+                prefix = make_prefix(depth);
+                printf("%s*** BAD END NS PREFIX: found=%s, expected=%s\n",
+                        prefix.string(), pr.string(), ns.prefix.string());
+            }
+            String8 uri = String8(block->getNamespaceUri(&len));
+            if (ns.uri != uri) {
+                prefix = make_prefix(depth);
+                printf("%s *** BAD END NS URI: found=%s, expected=%s\n",
+                        prefix.string(), uri.string(), ns.uri.string());
+            }
+            namespaces.pop();
+        } else if (code == ResXMLTree::TEXT) {
+            size_t len;
+            printf("%sC: \"%s\"\n", prefix.string(),
+                    ResTable::normalizeForOutput(String8(block->getText(&len)).string()).string());
+        }
+    }
+
+    block->restart();
+}
+
+status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
+                          bool stripAll, bool keepComments,
+                          const char** cDataTags)
+{
+    sp<XMLNode> root = XMLNode::parse(file);
+    if (root == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    root->removeWhitespace(stripAll, cDataTags);
+
+    NOISY(printf("Input XML from %s:\n", (const char*)file->getPrintableSource()));
+    NOISY(root->print());
+    sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
+    status_t err = root->flatten(rsc, !keepComments, false);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    NOISY(printf("Output XML:\n"));
+    NOISY(printXMLBlock(outTree));
+
+    return NO_ERROR;
+}
+
+sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
+{
+    char buf[16384];
+    int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
+    if (fd < 0) {
+        SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
+                strerror(errno));
+        return NULL;
+    }
+
+    XML_Parser parser = XML_ParserCreateNS(NULL, 1);
+    ParseState state;
+    state.filename = file->getPrintableSource();
+    state.parser = parser;
+    XML_SetUserData(parser, &state);
+    XML_SetElementHandler(parser, startElement, endElement);
+    XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
+    XML_SetCharacterDataHandler(parser, characterData);
+    XML_SetCommentHandler(parser, commentData);
+
+    ssize_t len;
+    bool done;
+    do {
+        len = read(fd, buf, sizeof(buf));
+        done = len < (ssize_t)sizeof(buf);
+        if (len < 0) {
+            SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno));
+            close(fd);
+            return NULL;
+        }
+        if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
+            SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error(
+                    "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
+            close(fd);
+            return NULL;
+        }
+    } while (!done);
+
+    XML_ParserFree(parser);
+    if (state.root == NULL) {
+        SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
+    }
+    close(fd);
+    return state.root;
+}
+
+XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace)
+    : mNextAttributeIndex(0x80000000)
+    , mFilename(filename)
+    , mStartLineNumber(0)
+    , mEndLineNumber(0)
+    , mUTF8(false)
+{
+    if (isNamespace) {
+        mNamespacePrefix = s1;
+        mNamespaceUri = s2;
+    } else {
+        mNamespaceUri = s1;
+        mElementName = s2;
+    }
+}
+
+XMLNode::XMLNode(const String8& filename)
+    : mFilename(filename)
+{
+    memset(&mCharsValue, 0, sizeof(mCharsValue));
+}
+
+XMLNode::type XMLNode::getType() const
+{
+    if (mElementName.size() != 0) {
+        return TYPE_ELEMENT;
+    }
+    if (mNamespaceUri.size() != 0) {
+        return TYPE_NAMESPACE;
+    }
+    return TYPE_CDATA;
+}
+
+const String16& XMLNode::getNamespacePrefix() const
+{
+    return mNamespacePrefix;
+}
+
+const String16& XMLNode::getNamespaceUri() const
+{
+    return mNamespaceUri;
+}
+
+const String16& XMLNode::getElementNamespace() const
+{
+    return mNamespaceUri;
+}
+
+const String16& XMLNode::getElementName() const
+{
+    return mElementName;
+}
+
+const Vector<sp<XMLNode> >& XMLNode::getChildren() const
+{
+    return mChildren;
+}
+
+const String8& XMLNode::getFilename() const
+{
+    return mFilename;
+}
+    
+const Vector<XMLNode::attribute_entry>&
+    XMLNode::getAttributes() const
+{
+    return mAttributes;
+}
+
+const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
+        const String16& name) const
+{
+    for (size_t i=0; i<mAttributes.size(); i++) {
+        const attribute_entry& ae(mAttributes.itemAt(i));
+        if (ae.ns == ns && ae.name == name) {
+            return &ae;
+        }
+    }
+    
+    return NULL;
+}
+
+XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
+        const String16& name)
+{
+    for (size_t i=0; i<mAttributes.size(); i++) {
+        attribute_entry * ae = &mAttributes.editItemAt(i);
+        if (ae->ns == ns && ae->name == name) {
+            return ae;
+        }
+    }
+
+    return NULL;
+}
+
+const String16& XMLNode::getCData() const
+{
+    return mChars;
+}
+
+const String16& XMLNode::getComment() const
+{
+    return mComment;
+}
+
+int32_t XMLNode::getStartLineNumber() const
+{
+    return mStartLineNumber;
+}
+
+int32_t XMLNode::getEndLineNumber() const
+{
+    return mEndLineNumber;
+}
+
+sp<XMLNode> XMLNode::searchElement(const String16& tagNamespace, const String16& tagName)
+{
+    if (getType() == XMLNode::TYPE_ELEMENT
+            && mNamespaceUri == tagNamespace
+            && mElementName == tagName) {
+        return this;
+    }
+    
+    for (size_t i=0; i<mChildren.size(); i++) {
+        sp<XMLNode> found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName);
+        if (found != NULL) {
+            return found;
+        }
+    }
+    
+    return NULL;
+}
+
+sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName)
+{
+    for (size_t i=0; i<mChildren.size(); i++) {
+        sp<XMLNode> child = mChildren.itemAt(i);
+        if (child->getType() == XMLNode::TYPE_ELEMENT
+                && child->mNamespaceUri == tagNamespace
+                && child->mElementName == tagName) {
+            return child;
+        }
+    }
+    
+    return NULL;
+}
+
+status_t XMLNode::addChild(const sp<XMLNode>& child)
+{
+    if (getType() == TYPE_CDATA) {
+        SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
+        return UNKNOWN_ERROR;
+    }
+    //printf("Adding child %p to parent %p\n", child.get(), this);
+    mChildren.add(child);
+    return NO_ERROR;
+}
+
+status_t XMLNode::insertChildAt(const sp<XMLNode>& child, size_t index)
+{
+    if (getType() == TYPE_CDATA) {
+        SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
+        return UNKNOWN_ERROR;
+    }
+    //printf("Adding child %p to parent %p\n", child.get(), this);
+    mChildren.insertAt(child, index);
+    return NO_ERROR;
+}
+
+status_t XMLNode::addAttribute(const String16& ns, const String16& name,
+                               const String16& value)
+{
+    if (getType() == TYPE_CDATA) {
+        SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node.");
+        return UNKNOWN_ERROR;
+    }
+
+    if (ns != RESOURCES_TOOLS_NAMESPACE) {
+        attribute_entry e;
+        e.index = mNextAttributeIndex++;
+        e.ns = ns;
+        e.name = name;
+        e.string = value;
+        mAttributes.add(e);
+        mAttributeOrder.add(e.index, mAttributes.size()-1);
+    }
+    return NO_ERROR;
+}
+
+void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId)
+{
+    attribute_entry& e = mAttributes.editItemAt(attrIdx);
+    if (e.nameResId) {
+        mAttributeOrder.removeItem(e.nameResId);
+    } else {
+        mAttributeOrder.removeItem(e.index);
+    }
+    NOISY(printf("Elem %s %s=\"%s\": set res id = 0x%08x\n",
+            String8(getElementName()).string(),
+            String8(mAttributes.itemAt(attrIdx).name).string(),
+            String8(mAttributes.itemAt(attrIdx).string).string(),
+            resId));
+    mAttributes.editItemAt(attrIdx).nameResId = resId;
+    mAttributeOrder.add(resId, attrIdx);
+}
+
+status_t XMLNode::appendChars(const String16& chars)
+{
+    if (getType() != TYPE_CDATA) {
+        SourcePos(mFilename, getStartLineNumber()).error("Adding characters to element node.");
+        return UNKNOWN_ERROR;
+    }
+    mChars.append(chars);
+    return NO_ERROR;
+}
+
+status_t XMLNode::appendComment(const String16& comment)
+{
+    if (mComment.size() > 0) {
+        mComment.append(String16("\n"));
+    }
+    mComment.append(comment);
+    return NO_ERROR;
+}
+
+void XMLNode::setStartLineNumber(int32_t line)
+{
+    mStartLineNumber = line;
+}
+
+void XMLNode::setEndLineNumber(int32_t line)
+{
+    mEndLineNumber = line;
+}
+
+void XMLNode::removeWhitespace(bool stripAll, const char** cDataTags)
+{
+    //printf("Removing whitespace in %s\n", String8(mElementName).string());
+    size_t N = mChildren.size();
+    if (cDataTags) {
+        String8 tag(mElementName);
+        const char** p = cDataTags;
+        while (*p) {
+            if (tag == *p) {
+                stripAll = false;
+                break;
+            }
+        }
+    }
+    for (size_t i=0; i<N; i++) {
+        sp<XMLNode> node = mChildren.itemAt(i);
+        if (node->getType() == TYPE_CDATA) {
+            // This is a CDATA node...
+            const char16_t* p = node->mChars.string();
+            while (*p != 0 && *p < 128 && isspace(*p)) {
+                p++;
+            }
+            //printf("Space ends at %d in \"%s\"\n",
+            //       (int)(p-node->mChars.string()),
+            //       String8(node->mChars).string());
+            if (*p == 0) {
+                if (stripAll) {
+                    // Remove this node!
+                    mChildren.removeAt(i);
+                    N--;
+                    i--;
+                } else {
+                    node->mChars = String16(" ");
+                }
+            } else {
+                // Compact leading/trailing whitespace.
+                const char16_t* e = node->mChars.string()+node->mChars.size()-1;
+                while (e > p && *e < 128 && isspace(*e)) {
+                    e--;
+                }
+                if (p > node->mChars.string()) {
+                    p--;
+                }
+                if (e < (node->mChars.string()+node->mChars.size()-1)) {
+                    e++;
+                }
+                if (p > node->mChars.string() ||
+                    e < (node->mChars.string()+node->mChars.size()-1)) {
+                    String16 tmp(p, e-p+1);
+                    node->mChars = tmp;
+                }
+            }
+        } else {
+            node->removeWhitespace(stripAll, cDataTags);
+        }
+    }
+}
+
+status_t XMLNode::parseValues(const sp<AaptAssets>& assets,
+                              ResourceTable* table)
+{
+    bool hasErrors = false;
+    
+    if (getType() == TYPE_ELEMENT) {
+        const size_t N = mAttributes.size();
+        String16 defPackage(assets->getPackage());
+        for (size_t i=0; i<N; i++) {
+            attribute_entry& e = mAttributes.editItemAt(i);
+            AccessorCookie ac(SourcePos(mFilename, getStartLineNumber()), String8(e.name),
+                    String8(e.string));
+            table->setCurrentXmlPos(SourcePos(mFilename, getStartLineNumber()));
+            if (!assets->getIncludedResources()
+                    .stringToValue(&e.value, &e.string,
+                                  e.string.string(), e.string.size(), true, true,
+                                  e.nameResId, NULL, &defPackage, table, &ac)) {
+                hasErrors = true;
+            }
+            NOISY(printf("Attr %s: type=0x%x, str=%s\n",
+                   String8(e.name).string(), e.value.dataType,
+                   String8(e.string).string()));
+        }
+    }
+    const size_t N = mChildren.size();
+    for (size_t i=0; i<N; i++) {
+        status_t err = mChildren.itemAt(i)->parseValues(assets, table);
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
+                                    const ResourceTable* table)
+{
+    bool hasErrors = false;
+    
+    if (getType() == TYPE_ELEMENT) {
+        String16 attr("attr");
+        const char* errorMsg;
+        const size_t N = mAttributes.size();
+        for (size_t i=0; i<N; i++) {
+            const attribute_entry& e = mAttributes.itemAt(i);
+            if (e.ns.size() <= 0) continue;
+            bool nsIsPublic;
+            String16 pkg(getNamespaceResourcePackage(String16(assets->getPackage()), e.ns, &nsIsPublic));
+            NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n",
+                    String8(getElementName()).string(),
+                    String8(e.name).string(),
+                    String8(e.string).string(),
+                    String8(e.ns).string(),
+                    (nsIsPublic) ? "public" : "private",
+                    String8(pkg).string()));
+            if (pkg.size() <= 0) continue;
+            uint32_t res = table != NULL
+                ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic)
+                : assets->getIncludedResources().
+                    identifierForName(e.name.string(), e.name.size(),
+                                      attr.string(), attr.size(),
+                                      pkg.string(), pkg.size());
+            if (res != 0) {
+                NOISY(printf("XML attribute name %s: resid=0x%08x\n",
+                             String8(e.name).string(), res));
+                setAttributeResID(i, res);
+            } else {
+                SourcePos(mFilename, getStartLineNumber()).error(
+                        "No resource identifier found for attribute '%s' in package '%s'\n",
+                        String8(e.name).string(), String8(pkg).string());
+                hasErrors = true;
+            }
+        }
+    }
+    const size_t N = mChildren.size();
+    for (size_t i=0; i<N; i++) {
+        status_t err = mChildren.itemAt(i)->assignResourceIds(assets, table);
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
+    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t XMLNode::flatten(const sp<AaptFile>& dest,
+        bool stripComments, bool stripRawValues) const
+{
+    StringPool strings(mUTF8);
+    Vector<uint32_t> resids;
+    
+    // First collect just the strings for attribute names that have a
+    // resource ID assigned to them.  This ensures that the resource ID
+    // array is compact, and makes it easier to deal with attribute names
+    // in different namespaces (and thus with different resource IDs).
+    collect_resid_strings(&strings, &resids);
+
+    // Next collect all remainibng strings.
+    collect_strings(&strings, &resids, stripComments, stripRawValues);
+
+#if 0  // No longer compiles
+    NOISY(printf("Found strings:\n");
+        const size_t N = strings.size();
+        for (size_t i=0; i<N; i++) {
+            printf("%s\n", String8(strings.entryAt(i).string).string());
+        }
+    );
+#endif    
+
+    sp<AaptFile> stringPool = strings.createStringBlock();
+    NOISY(aout << "String pool:"
+          << HexDump(stringPool->getData(), stringPool->getSize()) << endl);
+
+    ResXMLTree_header header;
+    memset(&header, 0, sizeof(header));
+    header.header.type = htods(RES_XML_TYPE);
+    header.header.headerSize = htods(sizeof(header));
+
+    const size_t basePos = dest->getSize();
+    dest->writeData(&header, sizeof(header));
+    dest->writeData(stringPool->getData(), stringPool->getSize());
+
+    // If we have resource IDs, write them.
+    if (resids.size() > 0) {
+        const size_t resIdsPos = dest->getSize();
+        const size_t resIdsSize =
+            sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
+        ResChunk_header* idsHeader = (ResChunk_header*)
+            (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
+        idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
+        idsHeader->headerSize = htods(sizeof(*idsHeader));
+        idsHeader->size = htodl(resIdsSize);
+        uint32_t* ids = (uint32_t*)(idsHeader+1);
+        for (size_t i=0; i<resids.size(); i++) {
+            *ids++ = htodl(resids[i]);
+        }
+    }
+
+    flatten_node(strings, dest, stripComments, stripRawValues);
+
+    void* data = dest->editData();
+    ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
+    size_t size = dest->getSize()-basePos;
+    hd->header.size = htodl(dest->getSize()-basePos);
+
+    NOISY(aout << "XML resource:"
+          << HexDump(dest->getData(), dest->getSize()) << endl);
+
+    #if PRINT_STRING_METRICS
+    fprintf(stderr, "**** total xml size: %d / %d%% strings (in %s)\n",
+        dest->getSize(), (stringPool->getSize()*100)/dest->getSize(),
+        dest->getPath().string());
+    #endif
+        
+    return NO_ERROR;
+}
+
+void XMLNode::print(int indent)
+{
+    String8 prefix;
+    int i;
+    for (i=0; i<indent; i++) {
+        prefix.append("  ");
+    }
+    if (getType() == TYPE_ELEMENT) {
+        String8 elemNs(getNamespaceUri());
+        if (elemNs.size() > 0) {
+            elemNs.append(":");
+        }
+        printf("%s E: %s%s", prefix.string(),
+               elemNs.string(), String8(getElementName()).string());
+        int N = mAttributes.size();
+        for (i=0; i<N; i++) {
+            ssize_t idx = mAttributeOrder.valueAt(i);
+            if (i == 0) {
+                printf(" / ");
+            } else {
+                printf(", ");
+            }
+            const attribute_entry& attr = mAttributes.itemAt(idx);
+            String8 attrNs(attr.ns);
+            if (attrNs.size() > 0) {
+                attrNs.append(":");
+            }
+            if (attr.nameResId) {
+                printf("%s%s(0x%08x)", attrNs.string(),
+                       String8(attr.name).string(), attr.nameResId);
+            } else {
+                printf("%s%s", attrNs.string(), String8(attr.name).string());
+            }
+            printf("=%s", String8(attr.string).string());
+        }
+        printf("\n");
+    } else if (getType() == TYPE_NAMESPACE) {
+        printf("%s N: %s=%s\n", prefix.string(),
+               getNamespacePrefix().size() > 0
+                    ? String8(getNamespacePrefix()).string() : "<DEF>",
+               String8(getNamespaceUri()).string());
+    } else {
+        printf("%s C: \"%s\"\n", prefix.string(), String8(getCData()).string());
+    }
+    int N = mChildren.size();
+    for (i=0; i<N; i++) {
+        mChildren.itemAt(i)->print(indent+1);
+    }
+}
+
+static void splitName(const char* name, String16* outNs, String16* outName)
+{
+    const char* p = name;
+    while (*p != 0 && *p != 1) {
+        p++;
+    }
+    if (*p == 0) {
+        *outNs = String16();
+        *outName = String16(name);
+    } else {
+        *outNs = String16(name, (p-name));
+        *outName = String16(p+1);
+    }
+}
+
+void XMLCALL
+XMLNode::startNamespace(void *userData, const char *prefix, const char *uri)
+{
+    NOISY_PARSE(printf("Start Namespace: %s %s\n", prefix, uri));
+    ParseState* st = (ParseState*)userData;
+    sp<XMLNode> node = XMLNode::newNamespace(st->filename, 
+            String16(prefix != NULL ? prefix : ""), String16(uri));
+    node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+    if (st->stack.size() > 0) {
+        st->stack.itemAt(st->stack.size()-1)->addChild(node);
+    } else {
+        st->root = node;
+    }
+    st->stack.push(node);
+}
+
+void XMLCALL
+XMLNode::startElement(void *userData, const char *name, const char **atts)
+{
+    NOISY_PARSE(printf("Start Element: %s\n", name));
+    ParseState* st = (ParseState*)userData;
+    String16 ns16, name16;
+    splitName(name, &ns16, &name16);
+    sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16);
+    node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+    if (st->pendingComment.size() > 0) {
+        node->appendComment(st->pendingComment);
+        st->pendingComment = String16();
+    }
+    if (st->stack.size() > 0) {
+        st->stack.itemAt(st->stack.size()-1)->addChild(node);
+    } else {
+        st->root = node;
+    }
+    st->stack.push(node);
+
+    for (int i = 0; atts[i]; i += 2) {
+        splitName(atts[i], &ns16, &name16);
+        node->addAttribute(ns16, name16, String16(atts[i+1]));
+    }
+}
+
+void XMLCALL
+XMLNode::characterData(void *userData, const XML_Char *s, int len)
+{
+    NOISY_PARSE(printf("CDATA: \"%s\"\n", String8(s, len).string()));
+    ParseState* st = (ParseState*)userData;
+    sp<XMLNode> node = NULL;
+    if (st->stack.size() == 0) {
+        return;
+    }
+    sp<XMLNode> parent = st->stack.itemAt(st->stack.size()-1);
+    if (parent != NULL && parent->getChildren().size() > 0) {
+        node = parent->getChildren()[parent->getChildren().size()-1];
+        if (node->getType() != TYPE_CDATA) {
+            // Last node is not CDATA, need to make a new node.
+            node = NULL;
+        }
+    }
+
+    if (node == NULL) {
+        node = XMLNode::newCData(st->filename);
+        node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+        parent->addChild(node);
+    }
+
+    node->appendChars(String16(s, len));
+}
+
+void XMLCALL
+XMLNode::endElement(void *userData, const char *name)
+{
+    NOISY_PARSE(printf("End Element: %s\n", name));
+    ParseState* st = (ParseState*)userData;
+    sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
+    node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
+    if (st->pendingComment.size() > 0) {
+        node->appendComment(st->pendingComment);
+        st->pendingComment = String16();
+    }
+    String16 ns16, name16;
+    splitName(name, &ns16, &name16);
+    LOG_ALWAYS_FATAL_IF(node->getElementNamespace() != ns16
+                        || node->getElementName() != name16,
+                        "Bad end element %s", name);
+    st->stack.pop();
+}
+
+void XMLCALL
+XMLNode::endNamespace(void *userData, const char *prefix)
+{
+    const char* nonNullPrefix = prefix != NULL ? prefix : "";
+    NOISY_PARSE(printf("End Namespace: %s\n", prefix));
+    ParseState* st = (ParseState*)userData;
+    sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
+    node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
+    LOG_ALWAYS_FATAL_IF(node->getNamespacePrefix() != String16(nonNullPrefix),
+                        "Bad end namespace %s", prefix);
+    st->stack.pop();
+}
+
+void XMLCALL
+XMLNode::commentData(void *userData, const char *comment)
+{
+    NOISY_PARSE(printf("Comment: %s\n", comment));
+    ParseState* st = (ParseState*)userData;
+    if (st->pendingComment.size() > 0) {
+        st->pendingComment.append(String16("\n"));
+    }
+    st->pendingComment.append(String16(comment));
+}
+
+status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
+        bool stripComments, bool stripRawValues) const
+{
+    collect_attr_strings(dest, outResIds, true);
+    
+    int i;
+    if (RESOURCES_TOOLS_NAMESPACE != mNamespaceUri) {
+        if (mNamespacePrefix.size() > 0) {
+            dest->add(mNamespacePrefix, true);
+        }
+        if (mNamespaceUri.size() > 0) {
+            dest->add(mNamespaceUri, true);
+        }
+    }
+    if (mElementName.size() > 0) {
+        dest->add(mElementName, true);
+    }
+
+    if (!stripComments && mComment.size() > 0) {
+        dest->add(mComment, true);
+    }
+
+    const int NA = mAttributes.size();
+
+    for (i=0; i<NA; i++) {
+        const attribute_entry& ae = mAttributes.itemAt(i);
+        if (ae.ns.size() > 0) {
+            dest->add(ae.ns, true);
+        }
+        if (!stripRawValues || ae.needStringValue()) {
+            dest->add(ae.string, true);
+        }
+        /*
+        if (ae.value.dataType == Res_value::TYPE_NULL
+                || ae.value.dataType == Res_value::TYPE_STRING) {
+            dest->add(ae.string, true);
+        }
+        */
+    }
+
+    if (mElementName.size() == 0) {
+        // If not an element, include the CDATA, even if it is empty.
+        dest->add(mChars, true);
+    }
+
+    const int NC = mChildren.size();
+
+    for (i=0; i<NC; i++) {
+        mChildren.itemAt(i)->collect_strings(dest, outResIds,
+                stripComments, stripRawValues);
+    }
+
+    return NO_ERROR;
+}
+
+status_t XMLNode::collect_attr_strings(StringPool* outPool,
+        Vector<uint32_t>* outResIds, bool allAttrs) const {
+    const int NA = mAttributes.size();
+
+    for (int i=0; i<NA; i++) {
+        const attribute_entry& attr = mAttributes.itemAt(i);
+        uint32_t id = attr.nameResId;
+        if (id || allAttrs) {
+            // See if we have already assigned this resource ID to a pooled
+            // string...
+            const Vector<size_t>* indices = outPool->offsetsForString(attr.name);
+            ssize_t idx = -1;
+            if (indices != NULL) {
+                const int NJ = indices->size();
+                const size_t NR = outResIds->size();
+                for (int j=0; j<NJ; j++) {
+                    size_t strIdx = indices->itemAt(j);
+                    if (strIdx >= NR) {
+                        if (id == 0) {
+                            // We don't need to assign a resource ID for this one.
+                            idx = strIdx;
+                            break;
+                        }
+                        // Just ignore strings that are out of range of
+                        // the currently assigned resource IDs...  we add
+                        // strings as we assign the first ID.
+                    } else if (outResIds->itemAt(strIdx) == id) {
+                        idx = strIdx;
+                        break;
+                    }
+                }
+            }
+            if (idx < 0) {
+                idx = outPool->add(attr.name);
+                NOISY(printf("Adding attr %s (resid 0x%08x) to pool: idx=%d\n",
+                        String8(attr.name).string(), id, idx));
+                if (id != 0) {
+                    while ((ssize_t)outResIds->size() <= idx) {
+                        outResIds->add(0);
+                    }
+                    outResIds->replaceAt(id, idx);
+                }
+            }
+            attr.namePoolIdx = idx;
+            NOISY(printf("String %s offset=0x%08x\n",
+                         String8(attr.name).string(), idx));
+        }
+    }
+
+    return NO_ERROR;
+}
+
+status_t XMLNode::collect_resid_strings(StringPool* outPool,
+        Vector<uint32_t>* outResIds) const
+{
+    collect_attr_strings(outPool, outResIds, false);
+
+    const int NC = mChildren.size();
+
+    for (int i=0; i<NC; i++) {
+        mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds);
+    }
+
+    return NO_ERROR;
+}
+
+status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
+        bool stripComments, bool stripRawValues) const
+{
+    ResXMLTree_node node;
+    ResXMLTree_cdataExt cdataExt;
+    ResXMLTree_namespaceExt namespaceExt;
+    ResXMLTree_attrExt attrExt;
+    const void* extData = NULL;
+    size_t extSize = 0;
+    ResXMLTree_attribute attr;
+    bool writeCurrentNode = true;
+
+    const size_t NA = mAttributes.size();
+    const size_t NC = mChildren.size();
+    size_t i;
+
+    LOG_ALWAYS_FATAL_IF(NA != mAttributeOrder.size(), "Attributes messed up!");
+
+    const String16 id16("id");
+    const String16 class16("class");
+    const String16 style16("style");
+
+    const type type = getType();
+
+    memset(&node, 0, sizeof(node));
+    memset(&attr, 0, sizeof(attr));
+    node.header.headerSize = htods(sizeof(node));
+    node.lineNumber = htodl(getStartLineNumber());
+    if (!stripComments) {
+        node.comment.index = htodl(
+            mComment.size() > 0 ? strings.offsetForString(mComment) : -1);
+        //if (mComment.size() > 0) {
+        //  printf("Flattening comment: %s\n", String8(mComment).string());
+        //}
+    } else {
+        node.comment.index = htodl((uint32_t)-1);
+    }
+    if (type == TYPE_ELEMENT) {
+        node.header.type = htods(RES_XML_START_ELEMENT_TYPE);
+        extData = &attrExt;
+        extSize = sizeof(attrExt);
+        memset(&attrExt, 0, sizeof(attrExt));
+        if (mNamespaceUri.size() > 0) {
+            attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri));
+        } else {
+            attrExt.ns.index = htodl((uint32_t)-1);
+        }
+        attrExt.name.index = htodl(strings.offsetForString(mElementName));
+        attrExt.attributeStart = htods(sizeof(attrExt));
+        attrExt.attributeSize = htods(sizeof(attr));
+        attrExt.attributeCount = htods(NA);
+        attrExt.idIndex = htods(0);
+        attrExt.classIndex = htods(0);
+        attrExt.styleIndex = htods(0);
+        for (i=0; i<NA; i++) {
+            ssize_t idx = mAttributeOrder.valueAt(i);
+            const attribute_entry& ae = mAttributes.itemAt(idx);
+            if (ae.ns.size() == 0) {
+                if (ae.name == id16) {
+                    attrExt.idIndex = htods(i+1);
+                } else if (ae.name == class16) {
+                    attrExt.classIndex = htods(i+1);
+                } else if (ae.name == style16) {
+                    attrExt.styleIndex = htods(i+1);
+                }
+            }
+        }
+    } else if (type == TYPE_NAMESPACE) {
+        if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) {
+            writeCurrentNode = false;
+        } else {
+            node.header.type = htods(RES_XML_START_NAMESPACE_TYPE);
+            extData = &namespaceExt;
+            extSize = sizeof(namespaceExt);
+            memset(&namespaceExt, 0, sizeof(namespaceExt));
+            if (mNamespacePrefix.size() > 0) {
+                namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
+            } else {
+                namespaceExt.prefix.index = htodl((uint32_t)-1);
+            }
+            namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
+            namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri));
+        }
+        LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!");
+    } else if (type == TYPE_CDATA) {
+        node.header.type = htods(RES_XML_CDATA_TYPE);
+        extData = &cdataExt;
+        extSize = sizeof(cdataExt);
+        memset(&cdataExt, 0, sizeof(cdataExt));
+        cdataExt.data.index = htodl(strings.offsetForString(mChars));
+        cdataExt.typedData.size = htods(sizeof(cdataExt.typedData));
+        cdataExt.typedData.res0 = 0;
+        cdataExt.typedData.dataType = mCharsValue.dataType;
+        cdataExt.typedData.data = htodl(mCharsValue.data);
+        LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!");
+    }
+
+    node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA));
+
+    if (writeCurrentNode) {
+        dest->writeData(&node, sizeof(node));
+        if (extSize > 0) {
+            dest->writeData(extData, extSize);
+        }
+    }
+
+    for (i=0; i<NA; i++) {
+        ssize_t idx = mAttributeOrder.valueAt(i);
+        const attribute_entry& ae = mAttributes.itemAt(idx);
+        if (ae.ns.size() > 0) {
+            attr.ns.index = htodl(strings.offsetForString(ae.ns));
+        } else {
+            attr.ns.index = htodl((uint32_t)-1);
+        }
+        attr.name.index = htodl(ae.namePoolIdx);
+
+        if (!stripRawValues || ae.needStringValue()) {
+            attr.rawValue.index = htodl(strings.offsetForString(ae.string));
+        } else {
+            attr.rawValue.index = htodl((uint32_t)-1);
+        }
+        attr.typedValue.size = htods(sizeof(attr.typedValue));
+        if (ae.value.dataType == Res_value::TYPE_NULL
+                || ae.value.dataType == Res_value::TYPE_STRING) {
+            attr.typedValue.res0 = 0;
+            attr.typedValue.dataType = Res_value::TYPE_STRING;
+            attr.typedValue.data = htodl(strings.offsetForString(ae.string));
+        } else {
+            attr.typedValue.res0 = 0;
+            attr.typedValue.dataType = ae.value.dataType;
+            attr.typedValue.data = htodl(ae.value.data);
+        }
+        dest->writeData(&attr, sizeof(attr));
+    }
+
+    for (i=0; i<NC; i++) {
+        status_t err = mChildren.itemAt(i)->flatten_node(strings, dest,
+                stripComments, stripRawValues);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    if (type == TYPE_ELEMENT) {
+        ResXMLTree_endElementExt endElementExt;
+        memset(&endElementExt, 0, sizeof(endElementExt));
+        node.header.type = htods(RES_XML_END_ELEMENT_TYPE);
+        node.header.size = htodl(sizeof(node)+sizeof(endElementExt));
+        node.lineNumber = htodl(getEndLineNumber());
+        node.comment.index = htodl((uint32_t)-1);
+        endElementExt.ns.index = attrExt.ns.index;
+        endElementExt.name.index = attrExt.name.index;
+        dest->writeData(&node, sizeof(node));
+        dest->writeData(&endElementExt, sizeof(endElementExt));
+    } else if (type == TYPE_NAMESPACE) {
+        if (writeCurrentNode) {
+            node.header.type = htods(RES_XML_END_NAMESPACE_TYPE);
+            node.lineNumber = htodl(getEndLineNumber());
+            node.comment.index = htodl((uint32_t)-1);
+            node.header.size = htodl(sizeof(node)+extSize);
+            dest->writeData(&node, sizeof(node));
+            dest->writeData(extData, extSize);
+        }
+    }
+
+    return NO_ERROR;
+}
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
new file mode 100644
index 0000000..05624b7
--- /dev/null
+++ b/tools/aapt/XMLNode.h
@@ -0,0 +1,202 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef XML_NODE_H
+#define XML_NODE_H
+
+#include "StringPool.h"
+#include "ResourceTable.h"
+
+class XMLNode;
+
+extern const char* const RESOURCES_ROOT_NAMESPACE;
+extern const char* const RESOURCES_ANDROID_NAMESPACE;
+
+bool isWhitespace(const char16_t* str);
+
+String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic = NULL);
+
+status_t parseStyledString(Bundle* bundle,
+                           const char* fileName,
+                           ResXMLTree* inXml,
+                           const String16& endTag,
+                           String16* outString,
+                           Vector<StringPool::entry_style_span>* outSpans,
+                           bool isFormatted,
+                           bool isPseudolocalizable);
+
+void printXMLBlock(ResXMLTree* block);
+
+status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
+                          bool stripAll=true, bool keepComments=false,
+                          const char** cDataTags=NULL);
+
+class XMLNode : public RefBase
+{
+public:
+    static sp<XMLNode> parse(const sp<AaptFile>& file);
+
+    static inline
+    sp<XMLNode> newNamespace(const String8& filename, const String16& prefix, const String16& uri) {
+        return new XMLNode(filename, prefix, uri, true);
+    }
+    
+    static inline
+    sp<XMLNode> newElement(const String8& filename, const String16& ns, const String16& name) {
+        return new XMLNode(filename, ns, name, false);
+    }
+    
+    static inline
+    sp<XMLNode> newCData(const String8& filename) {
+        return new XMLNode(filename);
+    }
+    
+    enum type {
+        TYPE_NAMESPACE,
+        TYPE_ELEMENT,
+        TYPE_CDATA
+    };
+    
+    type getType() const;
+    
+    const String16& getNamespacePrefix() const;
+    const String16& getNamespaceUri() const;
+    
+    const String16& getElementNamespace() const;
+    const String16& getElementName() const;
+    const Vector<sp<XMLNode> >& getChildren() const;
+
+    const String8& getFilename() const;
+    
+    struct attribute_entry {
+        attribute_entry() : index(~(uint32_t)0), nameResId(0)
+        {
+            value.dataType = Res_value::TYPE_NULL;
+        }
+
+        bool needStringValue() const {
+            return nameResId == 0
+                || value.dataType == Res_value::TYPE_NULL
+                || value.dataType == Res_value::TYPE_STRING;
+        }
+        
+        String16 ns;
+        String16 name;
+        String16 string;
+        Res_value value;
+        uint32_t index;
+        uint32_t nameResId;
+        mutable uint32_t namePoolIdx;
+    };
+
+    const Vector<attribute_entry>& getAttributes() const;
+
+    const attribute_entry* getAttribute(const String16& ns, const String16& name) const;
+    
+    attribute_entry* editAttribute(const String16& ns, const String16& name);
+
+    const String16& getCData() const;
+
+    const String16& getComment() const;
+
+    int32_t getStartLineNumber() const;
+    int32_t getEndLineNumber() const;
+
+    sp<XMLNode> searchElement(const String16& tagNamespace, const String16& tagName);
+    
+    sp<XMLNode> getChildElement(const String16& tagNamespace, const String16& tagName);
+    
+    status_t addChild(const sp<XMLNode>& child);
+
+    status_t insertChildAt(const sp<XMLNode>& child, size_t index);
+
+    status_t addAttribute(const String16& ns, const String16& name,
+                          const String16& value);
+
+    void setAttributeResID(size_t attrIdx, uint32_t resId);
+
+    status_t appendChars(const String16& chars);
+
+    status_t appendComment(const String16& comment);
+
+    void setStartLineNumber(int32_t line);
+    void setEndLineNumber(int32_t line);
+
+    void removeWhitespace(bool stripAll=true, const char** cDataTags=NULL);
+
+    void setUTF8(bool val) { mUTF8 = val; }
+
+    status_t parseValues(const sp<AaptAssets>& assets, ResourceTable* table);
+
+    status_t assignResourceIds(const sp<AaptAssets>& assets,
+                               const ResourceTable* table = NULL);
+
+    status_t flatten(const sp<AaptFile>& dest, bool stripComments,
+            bool stripRawValues) const;
+
+    void print(int indent=0);
+
+private:
+    struct ParseState
+    {
+        String8 filename;
+        XML_Parser parser;
+        sp<XMLNode> root;
+        Vector<sp<XMLNode> > stack;
+        String16 pendingComment;
+    };
+
+    static void XMLCALL
+    startNamespace(void *userData, const char *prefix, const char *uri);
+    static void XMLCALL
+    startElement(void *userData, const char *name, const char **atts);
+    static void XMLCALL
+    characterData(void *userData, const XML_Char *s, int len);
+    static void XMLCALL
+    endElement(void *userData, const char *name);
+    static void XMLCALL
+    endNamespace(void *userData, const char *prefix);
+    
+    static void XMLCALL
+    commentData(void *userData, const char *comment);
+    
+    // Creating an element node.
+    XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace);
+    
+    // Creating a CDATA node.
+    XMLNode(const String8& filename);
+    
+    status_t collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
+            bool stripComments, bool stripRawValues) const;
+
+    status_t collect_attr_strings(StringPool* outPool,
+        Vector<uint32_t>* outResIds, bool allAttrs) const;
+        
+    status_t collect_resid_strings(StringPool* outPool,
+            Vector<uint32_t>* outResIds) const;
+
+    status_t flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
+            bool stripComments, bool stripRawValues) const;
+
+    String16 mNamespacePrefix;
+    String16 mNamespaceUri;
+    String16 mElementName;
+    Vector<sp<XMLNode> > mChildren;
+    Vector<attribute_entry> mAttributes;
+    KeyedVector<uint32_t, uint32_t> mAttributeOrder;
+    uint32_t mNextAttributeIndex;
+    String16 mChars;
+    Res_value mCharsValue;
+    String16 mComment;
+    String8 mFilename;
+    int32_t mStartLineNumber;
+    int32_t mEndLineNumber;
+
+    // Encode compiled XML with UTF-8 StringPools?
+    bool mUTF8;
+};
+
+#endif
diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp
new file mode 100644
index 0000000..b575988
--- /dev/null
+++ b/tools/aapt/ZipEntry.cpp
@@ -0,0 +1,696 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to entries in a Zip archive.
+//
+
+#define LOG_TAG "zip"
+
+#include "ZipEntry.h"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+using namespace android;
+
+/*
+ * Initialize a new ZipEntry structure from a FILE* positioned at a
+ * CentralDirectoryEntry.
+ *
+ * On exit, the file pointer will be at the start of the next CDE or
+ * at the EOCD.
+ */
+status_t ZipEntry::initFromCDE(FILE* fp)
+{
+    status_t result;
+    long posn;
+    bool hasDD;
+
+    //ALOGV("initFromCDE ---\n");
+
+    /* read the CDE */
+    result = mCDE.read(fp);
+    if (result != NO_ERROR) {
+        ALOGD("mCDE.read failed\n");
+        return result;
+    }
+
+    //mCDE.dump();
+
+    /* using the info in the CDE, go load up the LFH */
+    posn = ftell(fp);
+    if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
+        ALOGD("local header seek failed (%ld)\n",
+            mCDE.mLocalHeaderRelOffset);
+        return UNKNOWN_ERROR;
+    }
+
+    result = mLFH.read(fp);
+    if (result != NO_ERROR) {
+        ALOGD("mLFH.read failed\n");
+        return result;
+    }
+
+    if (fseek(fp, posn, SEEK_SET) != 0)
+        return UNKNOWN_ERROR;
+
+    //mLFH.dump();
+
+    /*
+     * We *might* need to read the Data Descriptor at this point and
+     * integrate it into the LFH.  If this bit is set, the CRC-32,
+     * compressed size, and uncompressed size will be zero.  In practice
+     * these seem to be rare.
+     */
+    hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
+    if (hasDD) {
+        // do something clever
+        //ALOGD("+++ has data descriptor\n");
+    }
+
+    /*
+     * Sanity-check the LFH.  Note that this will fail if the "kUsesDataDescr"
+     * flag is set, because the LFH is incomplete.  (Not a problem, since we
+     * prefer the CDE values.)
+     */
+    if (!hasDD && !compareHeaders()) {
+        ALOGW("warning: header mismatch\n");
+        // keep going?
+    }
+
+    /*
+     * If the mVersionToExtract is greater than 20, we may have an
+     * issue unpacking the record -- could be encrypted, compressed
+     * with something we don't support, or use Zip64 extensions.  We
+     * can defer worrying about that to when we're extracting data.
+     */
+
+    return NO_ERROR;
+}
+
+/*
+ * Initialize a new entry.  Pass in the file name and an optional comment.
+ *
+ * Initializes the CDE and the LFH.
+ */
+void ZipEntry::initNew(const char* fileName, const char* comment)
+{
+    assert(fileName != NULL && *fileName != '\0');  // name required
+
+    /* most fields are properly initialized by constructor */
+    mCDE.mVersionMadeBy = kDefaultMadeBy;
+    mCDE.mVersionToExtract = kDefaultVersion;
+    mCDE.mCompressionMethod = kCompressStored;
+    mCDE.mFileNameLength = strlen(fileName);
+    if (comment != NULL)
+        mCDE.mFileCommentLength = strlen(comment);
+    mCDE.mExternalAttrs = 0x81b60020;   // matches what WinZip does
+
+    if (mCDE.mFileNameLength > 0) {
+        mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+        strcpy((char*) mCDE.mFileName, fileName);
+    }
+    if (mCDE.mFileCommentLength > 0) {
+        /* TODO: stop assuming null-terminated ASCII here? */
+        mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+        strcpy((char*) mCDE.mFileComment, comment);
+    }
+
+    copyCDEtoLFH();
+}
+
+/*
+ * Initialize a new entry, starting with the ZipEntry from a different
+ * archive.
+ *
+ * Initializes the CDE and the LFH.
+ */
+status_t ZipEntry::initFromExternal(const ZipFile* pZipFile,
+    const ZipEntry* pEntry)
+{
+    /*
+     * Copy everything in the CDE over, then fix up the hairy bits.
+     */
+    memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE));
+
+    if (mCDE.mFileNameLength > 0) {
+        mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+        if (mCDE.mFileName == NULL)
+            return NO_MEMORY;
+        strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName);
+    }
+    if (mCDE.mFileCommentLength > 0) {
+        mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+        if (mCDE.mFileComment == NULL)
+            return NO_MEMORY;
+        strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment);
+    }
+    if (mCDE.mExtraFieldLength > 0) {
+        /* we null-terminate this, though it may not be a string */
+        mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1];
+        if (mCDE.mExtraField == NULL)
+            return NO_MEMORY;
+        memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField,
+            mCDE.mExtraFieldLength+1);
+    }
+
+    /* construct the LFH from the CDE */
+    copyCDEtoLFH();
+
+    /*
+     * The LFH "extra" field is independent of the CDE "extra", so we
+     * handle it here.
+     */
+    assert(mLFH.mExtraField == NULL);
+    mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
+    if (mLFH.mExtraFieldLength > 0) {
+        mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
+        if (mLFH.mExtraField == NULL)
+            return NO_MEMORY;
+        memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
+            mLFH.mExtraFieldLength+1);
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Insert pad bytes in the LFH by tweaking the "extra" field.  This will
+ * potentially confuse something that put "extra" data in here earlier,
+ * but I can't find an actual problem.
+ */
+status_t ZipEntry::addPadding(int padding)
+{
+    if (padding <= 0)
+        return INVALID_OPERATION;
+
+    //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
+    //    padding, mLFH.mExtraFieldLength, mCDE.mFileName);
+
+    if (mLFH.mExtraFieldLength > 0) {
+        /* extend existing field */
+        unsigned char* newExtra;
+
+        newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
+        if (newExtra == NULL)
+            return NO_MEMORY;
+        memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
+        memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
+
+        delete[] mLFH.mExtraField;
+        mLFH.mExtraField = newExtra;
+        mLFH.mExtraFieldLength += padding;
+    } else {
+        /* create new field */
+        mLFH.mExtraField = new unsigned char[padding];
+        memset(mLFH.mExtraField, 0, padding);
+        mLFH.mExtraFieldLength = padding;
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Set the fields in the LFH equal to the corresponding fields in the CDE.
+ *
+ * This does not touch the LFH "extra" field.
+ */
+void ZipEntry::copyCDEtoLFH(void)
+{
+    mLFH.mVersionToExtract  = mCDE.mVersionToExtract;
+    mLFH.mGPBitFlag         = mCDE.mGPBitFlag;
+    mLFH.mCompressionMethod = mCDE.mCompressionMethod;
+    mLFH.mLastModFileTime   = mCDE.mLastModFileTime;
+    mLFH.mLastModFileDate   = mCDE.mLastModFileDate;
+    mLFH.mCRC32             = mCDE.mCRC32;
+    mLFH.mCompressedSize    = mCDE.mCompressedSize;
+    mLFH.mUncompressedSize  = mCDE.mUncompressedSize;
+    mLFH.mFileNameLength    = mCDE.mFileNameLength;
+    // the "extra field" is independent
+
+    delete[] mLFH.mFileName;
+    if (mLFH.mFileNameLength > 0) {
+        mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
+        strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
+    } else {
+        mLFH.mFileName = NULL;
+    }
+}
+
+/*
+ * Set some information about a file after we add it.
+ */
+void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+    int compressionMethod)
+{
+    mCDE.mCompressionMethod = compressionMethod;
+    mCDE.mCRC32 = crc32;
+    mCDE.mCompressedSize = compLen;
+    mCDE.mUncompressedSize = uncompLen;
+    mCDE.mCompressionMethod = compressionMethod;
+    if (compressionMethod == kCompressDeflated) {
+        mCDE.mGPBitFlag |= 0x0002;      // indicates maximum compression used
+    }
+    copyCDEtoLFH();
+}
+
+/*
+ * See if the data in mCDE and mLFH match up.  This is mostly useful for
+ * debugging these classes, but it can be used to identify damaged
+ * archives.
+ *
+ * Returns "false" if they differ.
+ */
+bool ZipEntry::compareHeaders(void) const
+{
+    if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
+        ALOGV("cmp: VersionToExtract\n");
+        return false;
+    }
+    if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
+        ALOGV("cmp: GPBitFlag\n");
+        return false;
+    }
+    if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
+        ALOGV("cmp: CompressionMethod\n");
+        return false;
+    }
+    if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
+        ALOGV("cmp: LastModFileTime\n");
+        return false;
+    }
+    if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
+        ALOGV("cmp: LastModFileDate\n");
+        return false;
+    }
+    if (mCDE.mCRC32 != mLFH.mCRC32) {
+        ALOGV("cmp: CRC32\n");
+        return false;
+    }
+    if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
+        ALOGV("cmp: CompressedSize\n");
+        return false;
+    }
+    if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
+        ALOGV("cmp: UncompressedSize\n");
+        return false;
+    }
+    if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
+        ALOGV("cmp: FileNameLength\n");
+        return false;
+    }
+#if 0       // this seems to be used for padding, not real data
+    if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
+        ALOGV("cmp: ExtraFieldLength\n");
+        return false;
+    }
+#endif
+    if (mCDE.mFileName != NULL) {
+        if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
+            ALOGV("cmp: FileName\n");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+/*
+ * Convert the DOS date/time stamp into a UNIX time stamp.
+ */
+time_t ZipEntry::getModWhen(void) const
+{
+    struct tm parts;
+
+    parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
+    parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
+    parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
+    parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
+    parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
+    parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
+    parts.tm_wday = parts.tm_yday = 0;
+    parts.tm_isdst = -1;        // DST info "not available"
+
+    return mktime(&parts);
+}
+
+/*
+ * Set the CDE/LFH timestamp from UNIX time.
+ */
+void ZipEntry::setModWhen(time_t when)
+{
+#ifdef HAVE_LOCALTIME_R
+    struct tm tmResult;
+#endif
+    time_t even;
+    unsigned short zdate, ztime;
+
+    struct tm* ptm;
+
+    /* round up to an even number of seconds */
+    even = (time_t)(((unsigned long)(when) + 1) & (~1));
+
+    /* expand */
+#ifdef HAVE_LOCALTIME_R
+    ptm = localtime_r(&even, &tmResult);
+#else
+    ptm = localtime(&even);
+#endif
+
+    int year;
+    year = ptm->tm_year;
+    if (year < 80)
+        year = 80;
+
+    zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
+    ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+
+    mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
+    mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
+}
+
+
+/*
+ * ===========================================================================
+ *      ZipEntry::LocalFileHeader
+ * ===========================================================================
+ */
+
+/*
+ * Read a local file header.
+ *
+ * On entry, "fp" points to the signature at the start of the header.
+ * On exit, "fp" points to the start of data.
+ */
+status_t ZipEntry::LocalFileHeader::read(FILE* fp)
+{
+    status_t result = NO_ERROR;
+    unsigned char buf[kLFHLen];
+
+    assert(mFileName == NULL);
+    assert(mExtraField == NULL);
+
+    if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+        ALOGD("whoops: didn't find expected signature\n");
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
+    mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
+    mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
+    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
+    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
+    mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
+    mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
+    mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
+    mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
+    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
+
+    // TODO: validate sizes
+
+    /* grab filename */
+    if (mFileNameLength != 0) {
+        mFileName = new unsigned char[mFileNameLength+1];
+        if (mFileName == NULL) {
+            result = NO_MEMORY;
+            goto bail;
+        }
+        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+            result = UNKNOWN_ERROR;
+            goto bail;
+        }
+        mFileName[mFileNameLength] = '\0';
+    }
+
+    /* grab extra field */
+    if (mExtraFieldLength != 0) {
+        mExtraField = new unsigned char[mExtraFieldLength+1];
+        if (mExtraField == NULL) {
+            result = NO_MEMORY;
+            goto bail;
+        }
+        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+            result = UNKNOWN_ERROR;
+            goto bail;
+        }
+        mExtraField[mExtraFieldLength] = '\0';
+    }
+
+bail:
+    return result;
+}
+
+/*
+ * Write a local file header.
+ */
+status_t ZipEntry::LocalFileHeader::write(FILE* fp)
+{
+    unsigned char buf[kLFHLen];
+
+    ZipEntry::putLongLE(&buf[0x00], kSignature);
+    ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
+    ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
+    ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
+    ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
+    ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
+    ZipEntry::putLongLE(&buf[0x0e], mCRC32);
+    ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
+    ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
+    ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
+    ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
+
+    if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
+        return UNKNOWN_ERROR;
+
+    /* write filename */
+    if (mFileNameLength != 0) {
+        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+            return UNKNOWN_ERROR;
+    }
+
+    /* write "extra field" */
+    if (mExtraFieldLength != 0) {
+        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+            return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+
+/*
+ * Dump the contents of a LocalFileHeader object.
+ */
+void ZipEntry::LocalFileHeader::dump(void) const
+{
+    ALOGD(" LocalFileHeader contents:\n");
+    ALOGD("  versToExt=%u gpBits=0x%04x compression=%u\n",
+        mVersionToExtract, mGPBitFlag, mCompressionMethod);
+    ALOGD("  modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+        mLastModFileTime, mLastModFileDate, mCRC32);
+    ALOGD("  compressedSize=%lu uncompressedSize=%lu\n",
+        mCompressedSize, mUncompressedSize);
+    ALOGD("  filenameLen=%u extraLen=%u\n",
+        mFileNameLength, mExtraFieldLength);
+    if (mFileName != NULL)
+        ALOGD("  filename: '%s'\n", mFileName);
+}
+
+
+/*
+ * ===========================================================================
+ *      ZipEntry::CentralDirEntry
+ * ===========================================================================
+ */
+
+/*
+ * Read the central dir entry that appears next in the file.
+ *
+ * On entry, "fp" should be positioned on the signature bytes for the
+ * entry.  On exit, "fp" will point at the signature word for the next
+ * entry or for the EOCD.
+ */
+status_t ZipEntry::CentralDirEntry::read(FILE* fp)
+{
+    status_t result = NO_ERROR;
+    unsigned char buf[kCDELen];
+
+    /* no re-use */
+    assert(mFileName == NULL);
+    assert(mExtraField == NULL);
+    assert(mFileComment == NULL);
+
+    if (fread(buf, 1, kCDELen, fp) != kCDELen) {
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+        ALOGD("Whoops: didn't find expected signature\n");
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
+    mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
+    mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
+    mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
+    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
+    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
+    mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
+    mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
+    mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
+    mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
+    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
+    mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
+    mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
+    mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
+    mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
+    mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
+
+    // TODO: validate sizes and offsets
+
+    /* grab filename */
+    if (mFileNameLength != 0) {
+        mFileName = new unsigned char[mFileNameLength+1];
+        if (mFileName == NULL) {
+            result = NO_MEMORY;
+            goto bail;
+        }
+        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+            result = UNKNOWN_ERROR;
+            goto bail;
+        }
+        mFileName[mFileNameLength] = '\0';
+    }
+
+    /* read "extra field" */
+    if (mExtraFieldLength != 0) {
+        mExtraField = new unsigned char[mExtraFieldLength+1];
+        if (mExtraField == NULL) {
+            result = NO_MEMORY;
+            goto bail;
+        }
+        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+            result = UNKNOWN_ERROR;
+            goto bail;
+        }
+        mExtraField[mExtraFieldLength] = '\0';
+    }
+
+
+    /* grab comment, if any */
+    if (mFileCommentLength != 0) {
+        mFileComment = new unsigned char[mFileCommentLength+1];
+        if (mFileComment == NULL) {
+            result = NO_MEMORY;
+            goto bail;
+        }
+        if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+        {
+            result = UNKNOWN_ERROR;
+            goto bail;
+        }
+        mFileComment[mFileCommentLength] = '\0';
+    }
+
+bail:
+    return result;
+}
+
+/*
+ * Write a central dir entry.
+ */
+status_t ZipEntry::CentralDirEntry::write(FILE* fp)
+{
+    unsigned char buf[kCDELen];
+
+    ZipEntry::putLongLE(&buf[0x00], kSignature);
+    ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
+    ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
+    ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
+    ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
+    ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
+    ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
+    ZipEntry::putLongLE(&buf[0x10], mCRC32);
+    ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
+    ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
+    ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
+    ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
+    ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
+    ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
+    ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
+    ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
+    ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
+
+    if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
+        return UNKNOWN_ERROR;
+
+    /* write filename */
+    if (mFileNameLength != 0) {
+        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+            return UNKNOWN_ERROR;
+    }
+
+    /* write "extra field" */
+    if (mExtraFieldLength != 0) {
+        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+            return UNKNOWN_ERROR;
+    }
+
+    /* write comment */
+    if (mFileCommentLength != 0) {
+        if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+            return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Dump the contents of a CentralDirEntry object.
+ */
+void ZipEntry::CentralDirEntry::dump(void) const
+{
+    ALOGD(" CentralDirEntry contents:\n");
+    ALOGD("  versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
+        mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
+    ALOGD("  modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+        mLastModFileTime, mLastModFileDate, mCRC32);
+    ALOGD("  compressedSize=%lu uncompressedSize=%lu\n",
+        mCompressedSize, mUncompressedSize);
+    ALOGD("  filenameLen=%u extraLen=%u commentLen=%u\n",
+        mFileNameLength, mExtraFieldLength, mFileCommentLength);
+    ALOGD("  diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
+        mDiskNumberStart, mInternalAttrs, mExternalAttrs,
+        mLocalHeaderRelOffset);
+
+    if (mFileName != NULL)
+        ALOGD("  filename: '%s'\n", mFileName);
+    if (mFileComment != NULL)
+        ALOGD("  comment: '%s'\n", mFileComment);
+}
+
diff --git a/tools/aapt/ZipEntry.h b/tools/aapt/ZipEntry.h
new file mode 100644
index 0000000..c2f3227
--- /dev/null
+++ b/tools/aapt/ZipEntry.h
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace android {
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself.  (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry).  The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+    friend class ZipFile;
+
+    ZipEntry(void)
+        : mDeleted(false), mMarked(false)
+        {}
+    ~ZipEntry(void) {}
+
+    /*
+     * Returns "true" if the data is compressed.
+     */
+    bool isCompressed(void) const {
+        return mCDE.mCompressionMethod != kCompressStored;
+    }
+    int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+    /*
+     * Return the uncompressed length.
+     */
+    off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+    /*
+     * Return the compressed length.  For uncompressed data, this returns
+     * the same thing as getUncompresesdLen().
+     */
+    off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+    /*
+     * Return the offset of the local file header.
+     */
+    off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+    /*
+     * Return the absolute file offset of the start of the compressed or
+     * uncompressed data.
+     */
+    off_t getFileOffset(void) const {
+        return mCDE.mLocalHeaderRelOffset +
+                LocalFileHeader::kLFHLen +
+                mLFH.mFileNameLength +
+                mLFH.mExtraFieldLength;
+    }
+
+    /*
+     * Return the data CRC.
+     */
+    unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+    /*
+     * Return file modification time in UNIX seconds-since-epoch.
+     */
+    time_t getModWhen(void) const;
+
+    /*
+     * Return the archived file name.
+     */
+    const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+    /*
+     * Application-defined "mark".  Can be useful when synchronizing the
+     * contents of an archive with contents on disk.
+     */
+    bool getMarked(void) const { return mMarked; }
+    void setMarked(bool val) { mMarked = val; }
+
+    /*
+     * Some basic functions for raw data manipulation.  "LE" means
+     * Little Endian.
+     */
+    static inline unsigned short getShortLE(const unsigned char* buf) {
+        return buf[0] | (buf[1] << 8);
+    }
+    static inline unsigned long getLongLE(const unsigned char* buf) {
+        return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+    }
+    static inline void putShortLE(unsigned char* buf, short val) {
+        buf[0] = (unsigned char) val;
+        buf[1] = (unsigned char) (val >> 8);
+    }
+    static inline void putLongLE(unsigned char* buf, long val) {
+        buf[0] = (unsigned char) val;
+        buf[1] = (unsigned char) (val >> 8);
+        buf[2] = (unsigned char) (val >> 16);
+        buf[3] = (unsigned char) (val >> 24);
+    }
+
+    /* defined for Zip archives */
+    enum {
+        kCompressStored     = 0,        // no compression
+        // shrunk           = 1,
+        // reduced 1        = 2,
+        // reduced 2        = 3,
+        // reduced 3        = 4,
+        // reduced 4        = 5,
+        // imploded         = 6,
+        // tokenized        = 7,
+        kCompressDeflated   = 8,        // standard deflate
+        // Deflate64        = 9,
+        // lib imploded     = 10,
+        // reserved         = 11,
+        // bzip2            = 12,
+    };
+
+    /*
+     * Deletion flag.  If set, the entry will be removed on the next
+     * call to "flush".
+     */
+    bool getDeleted(void) const { return mDeleted; }
+
+protected:
+    /*
+     * Initialize the structure from the file, which is pointing at
+     * our Central Directory entry.
+     */
+    status_t initFromCDE(FILE* fp);
+
+    /*
+     * Initialize the structure for a new file.  We need the filename
+     * and comment so that we can properly size the LFH area.  The
+     * filename is mandatory, the comment is optional.
+     */
+    void initNew(const char* fileName, const char* comment);
+
+    /*
+     * Initialize the structure with the contents of a ZipEntry from
+     * another file.
+     */
+    status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry);
+
+    /*
+     * Add some pad bytes to the LFH.  We do this by adding or resizing
+     * the "extra" field.
+     */
+    status_t addPadding(int padding);
+
+    /*
+     * Set information about the data for this entry.
+     */
+    void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+        int compressionMethod);
+
+    /*
+     * Set the modification date.
+     */
+    void setModWhen(time_t when);
+
+    /*
+     * Set the offset of the local file header, relative to the start of
+     * the current file.
+     */
+    void setLFHOffset(off_t offset) {
+        mCDE.mLocalHeaderRelOffset = (long) offset;
+    }
+
+    /* mark for deletion; used by ZipFile::remove() */
+    void setDeleted(void) { mDeleted = true; }
+
+private:
+    /* these are private and not defined */
+    ZipEntry(const ZipEntry& src);
+    ZipEntry& operator=(const ZipEntry& src);
+
+    /* returns "true" if the CDE and the LFH agree */
+    bool compareHeaders(void) const;
+    void copyCDEtoLFH(void);
+
+    bool        mDeleted;       // set if entry is pending deletion
+    bool        mMarked;        // app-defined marker
+
+    /*
+     * Every entry in the Zip archive starts off with one of these.
+     */
+    class LocalFileHeader {
+    public:
+        LocalFileHeader(void) :
+            mVersionToExtract(0),
+            mGPBitFlag(0),
+            mCompressionMethod(0),
+            mLastModFileTime(0),
+            mLastModFileDate(0),
+            mCRC32(0),
+            mCompressedSize(0),
+            mUncompressedSize(0),
+            mFileNameLength(0),
+            mExtraFieldLength(0),
+            mFileName(NULL),
+            mExtraField(NULL)
+        {}
+        virtual ~LocalFileHeader(void) {
+            delete[] mFileName;
+            delete[] mExtraField;
+        }
+
+        status_t read(FILE* fp);
+        status_t write(FILE* fp);
+
+        // unsigned long mSignature;
+        unsigned short  mVersionToExtract;
+        unsigned short  mGPBitFlag;
+        unsigned short  mCompressionMethod;
+        unsigned short  mLastModFileTime;
+        unsigned short  mLastModFileDate;
+        unsigned long   mCRC32;
+        unsigned long   mCompressedSize;
+        unsigned long   mUncompressedSize;
+        unsigned short  mFileNameLength;
+        unsigned short  mExtraFieldLength;
+        unsigned char*  mFileName;
+        unsigned char*  mExtraField;
+
+        enum {
+            kSignature      = 0x04034b50,
+            kLFHLen         = 30,       // LocalFileHdr len, excl. var fields
+        };
+
+        void dump(void) const;
+    };
+
+    /*
+     * Every entry in the Zip archive has one of these in the "central
+     * directory" at the end of the file.
+     */
+    class CentralDirEntry {
+    public:
+        CentralDirEntry(void) :
+            mVersionMadeBy(0),
+            mVersionToExtract(0),
+            mGPBitFlag(0),
+            mCompressionMethod(0),
+            mLastModFileTime(0),
+            mLastModFileDate(0),
+            mCRC32(0),
+            mCompressedSize(0),
+            mUncompressedSize(0),
+            mFileNameLength(0),
+            mExtraFieldLength(0),
+            mFileCommentLength(0),
+            mDiskNumberStart(0),
+            mInternalAttrs(0),
+            mExternalAttrs(0),
+            mLocalHeaderRelOffset(0),
+            mFileName(NULL),
+            mExtraField(NULL),
+            mFileComment(NULL)
+        {}
+        virtual ~CentralDirEntry(void) {
+            delete[] mFileName;
+            delete[] mExtraField;
+            delete[] mFileComment;
+        }
+
+        status_t read(FILE* fp);
+        status_t write(FILE* fp);
+
+        // unsigned long mSignature;
+        unsigned short  mVersionMadeBy;
+        unsigned short  mVersionToExtract;
+        unsigned short  mGPBitFlag;
+        unsigned short  mCompressionMethod;
+        unsigned short  mLastModFileTime;
+        unsigned short  mLastModFileDate;
+        unsigned long   mCRC32;
+        unsigned long   mCompressedSize;
+        unsigned long   mUncompressedSize;
+        unsigned short  mFileNameLength;
+        unsigned short  mExtraFieldLength;
+        unsigned short  mFileCommentLength;
+        unsigned short  mDiskNumberStart;
+        unsigned short  mInternalAttrs;
+        unsigned long   mExternalAttrs;
+        unsigned long   mLocalHeaderRelOffset;
+        unsigned char*  mFileName;
+        unsigned char*  mExtraField;
+        unsigned char*  mFileComment;
+
+        void dump(void) const;
+
+        enum {
+            kSignature      = 0x02014b50,
+            kCDELen         = 46,       // CentralDirEnt len, excl. var fields
+        };
+    };
+
+    enum {
+        //kDataDescriptorSignature  = 0x08074b50,   // currently unused
+        kDataDescriptorLen  = 16,           // four 32-bit fields
+
+        kDefaultVersion     = 20,           // need deflate, nothing much else
+        kDefaultMadeBy      = 0x0317,       // 03=UNIX, 17=spec v2.3
+        kUsesDataDescr      = 0x0008,       // GPBitFlag bit 3
+    };
+
+    LocalFileHeader     mLFH;
+    CentralDirEntry     mCDE;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp
new file mode 100644
index 0000000..8057068
--- /dev/null
+++ b/tools/aapt/ZipFile.cpp
@@ -0,0 +1,1297 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to Zip archives.
+//
+
+#define LOG_TAG "zip"
+
+#include <androidfw/ZipUtils.h>
+#include <utils/Log.h>
+
+#include "ZipFile.h"
+
+#include <zlib.h>
+#define DEF_MEM_LEVEL 8                // normally in zutil.h?
+
+#include <memory.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <assert.h>
+
+using namespace android;
+
+/*
+ * Some environments require the "b", some choke on it.
+ */
+#define FILE_OPEN_RO        "rb"
+#define FILE_OPEN_RW        "r+b"
+#define FILE_OPEN_RW_CREATE "w+b"
+
+/* should live somewhere else? */
+static status_t errnoToStatus(int err)
+{
+    if (err == ENOENT)
+        return NAME_NOT_FOUND;
+    else if (err == EACCES)
+        return PERMISSION_DENIED;
+    else
+        return UNKNOWN_ERROR;
+}
+
+/*
+ * Open a file and parse its guts.
+ */
+status_t ZipFile::open(const char* zipFileName, int flags)
+{
+    bool newArchive = false;
+
+    assert(mZipFp == NULL);     // no reopen
+
+    if ((flags & kOpenTruncate))
+        flags |= kOpenCreate;           // trunc implies create
+
+    if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
+        return INVALID_OPERATION;       // not both
+    if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
+        return INVALID_OPERATION;       // not neither
+    if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
+        return INVALID_OPERATION;       // create requires write
+
+    if (flags & kOpenTruncate) {
+        newArchive = true;
+    } else {
+        newArchive = (access(zipFileName, F_OK) != 0);
+        if (!(flags & kOpenCreate) && newArchive) {
+            /* not creating, must already exist */
+            ALOGD("File %s does not exist", zipFileName);
+            return NAME_NOT_FOUND;
+        }
+    }
+
+    /* open the file */
+    const char* openflags;
+    if (flags & kOpenReadWrite) {
+        if (newArchive)
+            openflags = FILE_OPEN_RW_CREATE;
+        else
+            openflags = FILE_OPEN_RW;
+    } else {
+        openflags = FILE_OPEN_RO;
+    }
+    mZipFp = fopen(zipFileName, openflags);
+    if (mZipFp == NULL) {
+        int err = errno;
+        ALOGD("fopen failed: %d\n", err);
+        return errnoToStatus(err);
+    }
+
+    status_t result;
+    if (!newArchive) {
+        /*
+         * Load the central directory.  If that fails, then this probably
+         * isn't a Zip archive.
+         */
+        result = readCentralDir();
+    } else {
+        /*
+         * Newly-created.  The EndOfCentralDir constructor actually
+         * sets everything to be the way we want it (all zeroes).  We
+         * set mNeedCDRewrite so that we create *something* if the
+         * caller doesn't add any files.  (We could also just unlink
+         * the file if it's brand new and nothing was added, but that's
+         * probably doing more than we really should -- the user might
+         * have a need for empty zip files.)
+         */
+        mNeedCDRewrite = true;
+        result = NO_ERROR;
+    }
+
+    if (flags & kOpenReadOnly)
+        mReadOnly = true;
+    else
+        assert(!mReadOnly);
+
+    return result;
+}
+
+/*
+ * Return the Nth entry in the archive.
+ */
+ZipEntry* ZipFile::getEntryByIndex(int idx) const
+{
+    if (idx < 0 || idx >= (int) mEntries.size())
+        return NULL;
+
+    return mEntries[idx];
+}
+
+/*
+ * Find an entry by name.
+ */
+ZipEntry* ZipFile::getEntryByName(const char* fileName) const
+{
+    /*
+     * Do a stupid linear string-compare search.
+     *
+     * There are various ways to speed this up, especially since it's rare
+     * to intermingle changes to the archive with "get by name" calls.  We
+     * don't want to sort the mEntries vector itself, however, because
+     * it's used to recreate the Central Directory.
+     *
+     * (Hash table works, parallel list of pointers in sorted order is good.)
+     */
+    int idx;
+
+    for (idx = mEntries.size()-1; idx >= 0; idx--) {
+        ZipEntry* pEntry = mEntries[idx];
+        if (!pEntry->getDeleted() &&
+            strcmp(fileName, pEntry->getFileName()) == 0)
+        {
+            return pEntry;
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ * Empty the mEntries vector.
+ */
+void ZipFile::discardEntries(void)
+{
+    int count = mEntries.size();
+
+    while (--count >= 0)
+        delete mEntries[count];
+
+    mEntries.clear();
+}
+
+
+/*
+ * Find the central directory and read the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end.  In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards.  The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly.  If the wrong value ends up in the EOCD
+ * area, we're hosed.  This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+status_t ZipFile::readCentralDir(void)
+{
+    status_t result = NO_ERROR;
+    unsigned char* buf = NULL;
+    off_t fileLength, seekStart;
+    long readAmount;
+    int i;
+
+    fseek(mZipFp, 0, SEEK_END);
+    fileLength = ftell(mZipFp);
+    rewind(mZipFp);
+
+    /* too small to be a ZIP archive? */
+    if (fileLength < EndOfCentralDir::kEOCDLen) {
+        ALOGD("Length is %ld -- too small\n", (long)fileLength);
+        result = INVALID_OPERATION;
+        goto bail;
+    }
+
+    buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
+    if (buf == NULL) {
+        ALOGD("Failure allocating %d bytes for EOCD search",
+             EndOfCentralDir::kMaxEOCDSearch);
+        result = NO_MEMORY;
+        goto bail;
+    }
+
+    if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
+        seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
+        readAmount = EndOfCentralDir::kMaxEOCDSearch;
+    } else {
+        seekStart = 0;
+        readAmount = (long) fileLength;
+    }
+    if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
+        ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    /* read the last part of the file into the buffer */
+    if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
+        ALOGD("short file? wanted %ld\n", readAmount);
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    /* find the end-of-central-dir magic */
+    for (i = readAmount - 4; i >= 0; i--) {
+        if (buf[i] == 0x50 &&
+            ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
+        {
+            ALOGV("+++ Found EOCD at buf+%d\n", i);
+            break;
+        }
+    }
+    if (i < 0) {
+        ALOGD("EOCD not found, not Zip\n");
+        result = INVALID_OPERATION;
+        goto bail;
+    }
+
+    /* extract eocd values */
+    result = mEOCD.readBuf(buf + i, readAmount - i);
+    if (result != NO_ERROR) {
+        ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
+        goto bail;
+    }
+    //mEOCD.dump();
+
+    if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
+        mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
+    {
+        ALOGD("Archive spanning not supported\n");
+        result = INVALID_OPERATION;
+        goto bail;
+    }
+
+    /*
+     * So far so good.  "mCentralDirSize" is the size in bytes of the
+     * central directory, so we can just seek back that far to find it.
+     * We can also seek forward mCentralDirOffset bytes from the
+     * start of the file.
+     *
+     * We're not guaranteed to have the rest of the central dir in the
+     * buffer, nor are we guaranteed that the central dir will have any
+     * sort of convenient size.  We need to skip to the start of it and
+     * read the header, then the other goodies.
+     *
+     * The only thing we really need right now is the file comment, which
+     * we're hoping to preserve.
+     */
+    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+        ALOGD("Failure seeking to central dir offset %ld\n",
+             mEOCD.mCentralDirOffset);
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    /*
+     * Loop through and read the central dir entries.
+     */
+    ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
+    int entry;
+    for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
+        ZipEntry* pEntry = new ZipEntry;
+
+        result = pEntry->initFromCDE(mZipFp);
+        if (result != NO_ERROR) {
+            ALOGD("initFromCDE failed\n");
+            delete pEntry;
+            goto bail;
+        }
+
+        mEntries.add(pEntry);
+    }
+
+
+    /*
+     * If all went well, we should now be back at the EOCD.
+     */
+    {
+        unsigned char checkBuf[4];
+        if (fread(checkBuf, 1, 4, mZipFp) != 4) {
+            ALOGD("EOCD check read failed\n");
+            result = INVALID_OPERATION;
+            goto bail;
+        }
+        if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
+            ALOGD("EOCD read check failed\n");
+            result = UNKNOWN_ERROR;
+            goto bail;
+        }
+        ALOGV("+++ EOCD read check passed\n");
+    }
+
+bail:
+    delete[] buf;
+    return result;
+}
+
+
+/*
+ * Add a new file to the archive.
+ *
+ * This requires creating and populating a ZipEntry structure, and copying
+ * the data into the file at the appropriate position.  The "appropriate
+ * position" is the current location of the central directory, which we
+ * casually overwrite (we can put it back later).
+ *
+ * If we were concerned about safety, we would want to make all changes
+ * in a temp file and then overwrite the original after everything was
+ * safely written.  Not really a concern for us.
+ */
+status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
+    const char* storageName, int sourceType, int compressionMethod,
+    ZipEntry** ppEntry)
+{
+    ZipEntry* pEntry = NULL;
+    status_t result = NO_ERROR;
+    long lfhPosn, startPosn, endPosn, uncompressedLen;
+    FILE* inputFp = NULL;
+    unsigned long crc;
+    time_t modWhen;
+
+    if (mReadOnly)
+        return INVALID_OPERATION;
+
+    assert(compressionMethod == ZipEntry::kCompressDeflated ||
+           compressionMethod == ZipEntry::kCompressStored);
+
+    /* make sure we're in a reasonable state */
+    assert(mZipFp != NULL);
+    assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+    /* make sure it doesn't already exist */
+    if (getEntryByName(storageName) != NULL)
+        return ALREADY_EXISTS;
+
+    if (!data) {
+        inputFp = fopen(fileName, FILE_OPEN_RO);
+        if (inputFp == NULL)
+            return errnoToStatus(errno);
+    }
+
+    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    pEntry = new ZipEntry;
+    pEntry->initNew(storageName, NULL);
+
+    /*
+     * From here on out, failures are more interesting.
+     */
+    mNeedCDRewrite = true;
+
+    /*
+     * Write the LFH, even though it's still mostly blank.  We need it
+     * as a place-holder.  In theory the LFH isn't necessary, but in
+     * practice some utilities demand it.
+     */
+    lfhPosn = ftell(mZipFp);
+    pEntry->mLFH.write(mZipFp);
+    startPosn = ftell(mZipFp);
+
+    /*
+     * Copy the data in, possibly compressing it as we go.
+     */
+    if (sourceType == ZipEntry::kCompressStored) {
+        if (compressionMethod == ZipEntry::kCompressDeflated) {
+            bool failed = false;
+            result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
+            if (result != NO_ERROR) {
+                ALOGD("compression failed, storing\n");
+                failed = true;
+            } else {
+                /*
+                 * Make sure it has compressed "enough".  This probably ought
+                 * to be set through an API call, but I don't expect our
+                 * criteria to change over time.
+                 */
+                long src = inputFp ? ftell(inputFp) : size;
+                long dst = ftell(mZipFp) - startPosn;
+                if (dst + (dst / 10) > src) {
+                    ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
+                        src, dst);
+                    failed = true;
+                }
+            }
+
+            if (failed) {
+                compressionMethod = ZipEntry::kCompressStored;
+                if (inputFp) rewind(inputFp);
+                fseek(mZipFp, startPosn, SEEK_SET);
+                /* fall through to kCompressStored case */
+            }
+        }
+        /* handle "no compression" request, or failed compression from above */
+        if (compressionMethod == ZipEntry::kCompressStored) {
+            if (inputFp) {
+                result = copyFpToFp(mZipFp, inputFp, &crc);
+            } else {
+                result = copyDataToFp(mZipFp, data, size, &crc);
+            }
+            if (result != NO_ERROR) {
+                // don't need to truncate; happens in CDE rewrite
+                ALOGD("failed copying data in\n");
+                goto bail;
+            }
+        }
+
+        // currently seeked to end of file
+        uncompressedLen = inputFp ? ftell(inputFp) : size;
+    } else if (sourceType == ZipEntry::kCompressDeflated) {
+        /* we should support uncompressed-from-compressed, but it's not
+         * important right now */
+        assert(compressionMethod == ZipEntry::kCompressDeflated);
+
+        bool scanResult;
+        int method;
+        long compressedLen;
+
+        scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
+                        &compressedLen, &crc);
+        if (!scanResult || method != ZipEntry::kCompressDeflated) {
+            ALOGD("this isn't a deflated gzip file?");
+            result = UNKNOWN_ERROR;
+            goto bail;
+        }
+
+        result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
+        if (result != NO_ERROR) {
+            ALOGD("failed copying gzip data in\n");
+            goto bail;
+        }
+    } else {
+        assert(false);
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    /*
+     * We could write the "Data Descriptor", but there doesn't seem to
+     * be any point since we're going to go back and write the LFH.
+     *
+     * Update file offsets.
+     */
+    endPosn = ftell(mZipFp);            // seeked to end of compressed data
+
+    /*
+     * Success!  Fill out new values.
+     */
+    pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
+        compressionMethod);
+    modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
+    pEntry->setModWhen(modWhen);
+    pEntry->setLFHOffset(lfhPosn);
+    mEOCD.mNumEntries++;
+    mEOCD.mTotalNumEntries++;
+    mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
+    mEOCD.mCentralDirOffset = endPosn;
+
+    /*
+     * Go back and write the LFH.
+     */
+    if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+    pEntry->mLFH.write(mZipFp);
+
+    /*
+     * Add pEntry to the list.
+     */
+    mEntries.add(pEntry);
+    if (ppEntry != NULL)
+        *ppEntry = pEntry;
+    pEntry = NULL;
+
+bail:
+    if (inputFp != NULL)
+        fclose(inputFp);
+    delete pEntry;
+    return result;
+}
+
+/*
+ * Add an entry by copying it from another zip file.  If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+    int padding, ZipEntry** ppEntry)
+{
+    ZipEntry* pEntry = NULL;
+    status_t result;
+    long lfhPosn, endPosn;
+
+    if (mReadOnly)
+        return INVALID_OPERATION;
+
+    /* make sure we're in a reasonable state */
+    assert(mZipFp != NULL);
+    assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    pEntry = new ZipEntry;
+    if (pEntry == NULL) {
+        result = NO_MEMORY;
+        goto bail;
+    }
+
+    result = pEntry->initFromExternal(pSourceZip, pSourceEntry);
+    if (result != NO_ERROR)
+        goto bail;
+    if (padding != 0) {
+        result = pEntry->addPadding(padding);
+        if (result != NO_ERROR)
+            goto bail;
+    }
+
+    /*
+     * From here on out, failures are more interesting.
+     */
+    mNeedCDRewrite = true;
+
+    /*
+     * Write the LFH.  Since we're not recompressing the data, we already
+     * have all of the fields filled out.
+     */
+    lfhPosn = ftell(mZipFp);
+    pEntry->mLFH.write(mZipFp);
+
+    /*
+     * Copy the data over.
+     *
+     * If the "has data descriptor" flag is set, we want to copy the DD
+     * fields as well.  This is a fixed-size area immediately following
+     * the data.
+     */
+    if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
+    {
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    off_t copyLen;
+    copyLen = pSourceEntry->getCompressedLen();
+    if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
+        copyLen += ZipEntry::kDataDescriptorLen;
+
+    if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
+        != NO_ERROR)
+    {
+        ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
+        result = UNKNOWN_ERROR;
+        goto bail;
+    }
+
+    /*
+     * Update file offsets.
+     */
+    endPosn = ftell(mZipFp);
+
+    /*
+     * Success!  Fill out new values.
+     */
+    pEntry->setLFHOffset(lfhPosn);      // sets mCDE.mLocalHeaderRelOffset
+    mEOCD.mNumEntries++;
+    mEOCD.mTotalNumEntries++;
+    mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
+    mEOCD.mCentralDirOffset = endPosn;
+
+    /*
+     * Add pEntry to the list.
+     */
+    mEntries.add(pEntry);
+    if (ppEntry != NULL)
+        *ppEntry = pEntry;
+    pEntry = NULL;
+
+    result = NO_ERROR;
+
+bail:
+    delete pEntry;
+    return result;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data.
+ */
+status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
+{
+    unsigned char tmpBuf[32768];
+    size_t count;
+
+    *pCRC32 = crc32(0L, Z_NULL, 0);
+
+    while (1) {
+        count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
+        if (ferror(srcFp) || ferror(dstFp))
+            return errnoToStatus(errno);
+        if (count == 0)
+            break;
+
+        *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+        if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+            ALOGD("fwrite %d bytes failed\n", (int) count);
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "dstFp" will be seeked immediately past the data.
+ */
+status_t ZipFile::copyDataToFp(FILE* dstFp,
+    const void* data, size_t size, unsigned long* pCRC32)
+{
+    size_t count;
+
+    *pCRC32 = crc32(0L, Z_NULL, 0);
+    if (size > 0) {
+        *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
+        if (fwrite(data, 1, size, dstFp) != size) {
+            ALOGD("fwrite %d bytes failed\n", (int) size);
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Copy some of the bytes in "src" to "dst".
+ *
+ * If "pCRC32" is NULL, the CRC will not be computed.
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data just written.
+ */
+status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+    unsigned long* pCRC32)
+{
+    unsigned char tmpBuf[32768];
+    size_t count;
+
+    if (pCRC32 != NULL)
+        *pCRC32 = crc32(0L, Z_NULL, 0);
+
+    while (length) {
+        long readSize;
+        
+        readSize = sizeof(tmpBuf);
+        if (readSize > length)
+            readSize = length;
+
+        count = fread(tmpBuf, 1, readSize, srcFp);
+        if ((long) count != readSize) {     // error or unexpected EOF
+            ALOGD("fread %d bytes failed\n", (int) readSize);
+            return UNKNOWN_ERROR;
+        }
+
+        if (pCRC32 != NULL)
+            *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+        if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+            ALOGD("fwrite %d bytes failed\n", (int) count);
+            return UNKNOWN_ERROR;
+        }
+
+        length -= readSize;
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Compress all of the data in "srcFp" and write it to "dstFp".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the compressed data.
+ */
+status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
+    const void* data, size_t size, unsigned long* pCRC32)
+{
+    status_t result = NO_ERROR;
+    const size_t kBufSize = 32768;
+    unsigned char* inBuf = NULL;
+    unsigned char* outBuf = NULL;
+    z_stream zstream;
+    bool atEof = false;     // no feof() aviailable yet
+    unsigned long crc;
+    int zerr;
+
+    /*
+     * Create an input buffer and an output buffer.
+     */
+    inBuf = new unsigned char[kBufSize];
+    outBuf = new unsigned char[kBufSize];
+    if (inBuf == NULL || outBuf == NULL) {
+        result = NO_MEMORY;
+        goto bail;
+    }
+
+    /*
+     * Initialize the zlib stream.
+     */
+    memset(&zstream, 0, sizeof(zstream));
+    zstream.zalloc = Z_NULL;
+    zstream.zfree = Z_NULL;
+    zstream.opaque = Z_NULL;
+    zstream.next_in = NULL;
+    zstream.avail_in = 0;
+    zstream.next_out = outBuf;
+    zstream.avail_out = kBufSize;
+    zstream.data_type = Z_UNKNOWN;
+
+    zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
+        Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+    if (zerr != Z_OK) {
+        result = UNKNOWN_ERROR;
+        if (zerr == Z_VERSION_ERROR) {
+            ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+                ZLIB_VERSION);
+        } else {
+            ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
+        }
+        goto bail;
+    }
+
+    crc = crc32(0L, Z_NULL, 0);
+
+    /*
+     * Loop while we have data.
+     */
+    do {
+        size_t getSize;
+        int flush;
+
+        /* only read if the input buffer is empty */
+        if (zstream.avail_in == 0 && !atEof) {
+            ALOGV("+++ reading %d bytes\n", (int)kBufSize);
+            if (data) {
+                getSize = size > kBufSize ? kBufSize : size;
+                memcpy(inBuf, data, getSize);
+                data = ((const char*)data) + getSize;
+                size -= getSize;
+            } else {
+                getSize = fread(inBuf, 1, kBufSize, srcFp);
+                if (ferror(srcFp)) {
+                    ALOGD("deflate read failed (errno=%d)\n", errno);
+                    goto z_bail;
+                }
+            }
+            if (getSize < kBufSize) {
+                ALOGV("+++  got %d bytes, EOF reached\n",
+                    (int)getSize);
+                atEof = true;
+            }
+
+            crc = crc32(crc, inBuf, getSize);
+
+            zstream.next_in = inBuf;
+            zstream.avail_in = getSize;
+        }
+
+        if (atEof)
+            flush = Z_FINISH;       /* tell zlib that we're done */
+        else
+            flush = Z_NO_FLUSH;     /* more to come! */
+
+        zerr = deflate(&zstream, flush);
+        if (zerr != Z_OK && zerr != Z_STREAM_END) {
+            ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
+            result = UNKNOWN_ERROR;
+            goto z_bail;
+        }
+
+        /* write when we're full or when we're done */
+        if (zstream.avail_out == 0 ||
+            (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
+        {
+            ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
+            if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
+                (size_t)(zstream.next_out - outBuf))
+            {
+                ALOGD("write %d failed in deflate\n",
+                    (int) (zstream.next_out - outBuf));
+                goto z_bail;
+            }
+
+            zstream.next_out = outBuf;
+            zstream.avail_out = kBufSize;
+        }
+    } while (zerr == Z_OK);
+
+    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
+
+    *pCRC32 = crc;
+
+z_bail:
+    deflateEnd(&zstream);        /* free up any allocated structures */
+
+bail:
+    delete[] inBuf;
+    delete[] outBuf;
+
+    return result;
+}
+
+/*
+ * Mark an entry as deleted.
+ *
+ * We will eventually need to crunch the file down, but if several files
+ * are being removed (perhaps as part of an "update" process) we can make
+ * things considerably faster by deferring the removal to "flush" time.
+ */
+status_t ZipFile::remove(ZipEntry* pEntry)
+{
+    /*
+     * Should verify that pEntry is actually part of this archive, and
+     * not some stray ZipEntry from a different file.
+     */
+
+    /* mark entry as deleted, and mark archive as dirty */
+    pEntry->setDeleted();
+    mNeedCDRewrite = true;
+    return NO_ERROR;
+}
+
+/*
+ * Flush any pending writes.
+ *
+ * In particular, this will crunch out deleted entries, and write the
+ * Central Directory and EOCD if we have stomped on them.
+ */
+status_t ZipFile::flush(void)
+{
+    status_t result = NO_ERROR;
+    long eocdPosn;
+    int i, count;
+
+    if (mReadOnly)
+        return INVALID_OPERATION;
+    if (!mNeedCDRewrite)
+        return NO_ERROR;
+
+    assert(mZipFp != NULL);
+
+    result = crunchArchive();
+    if (result != NO_ERROR)
+        return result;
+
+    if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
+        return UNKNOWN_ERROR;
+
+    count = mEntries.size();
+    for (i = 0; i < count; i++) {
+        ZipEntry* pEntry = mEntries[i];
+        pEntry->mCDE.write(mZipFp);
+    }
+
+    eocdPosn = ftell(mZipFp);
+    mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
+
+    mEOCD.write(mZipFp);
+
+    /*
+     * If we had some stuff bloat up during compression and get replaced
+     * with plain files, or if we deleted some entries, there's a lot
+     * of wasted space at the end of the file.  Remove it now.
+     */
+    if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
+        ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
+        // not fatal
+    }
+
+    /* should we clear the "newly added" flag in all entries now? */
+
+    mNeedCDRewrite = false;
+    return NO_ERROR;
+}
+
+/*
+ * Crunch deleted files out of an archive by shifting the later files down.
+ *
+ * Because we're not using a temp file, we do the operation inside the
+ * current file.
+ */
+status_t ZipFile::crunchArchive(void)
+{
+    status_t result = NO_ERROR;
+    int i, count;
+    long delCount, adjust;
+
+#if 0
+    printf("CONTENTS:\n");
+    for (i = 0; i < (int) mEntries.size(); i++) {
+        printf(" %d: lfhOff=%ld del=%d\n",
+            i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
+    }
+    printf("  END is %ld\n", (long) mEOCD.mCentralDirOffset);
+#endif
+
+    /*
+     * Roll through the set of files, shifting them as appropriate.  We
+     * could probably get a slight performance improvement by sliding
+     * multiple files down at once (because we could use larger reads
+     * when operating on batches of small files), but it's not that useful.
+     */
+    count = mEntries.size();
+    delCount = adjust = 0;
+    for (i = 0; i < count; i++) {
+        ZipEntry* pEntry = mEntries[i];
+        long span;
+
+        if (pEntry->getLFHOffset() != 0) {
+            long nextOffset;
+
+            /* Get the length of this entry by finding the offset
+             * of the next entry.  Directory entries don't have
+             * file offsets, so we need to find the next non-directory
+             * entry.
+             */
+            nextOffset = 0;
+            for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
+                nextOffset = mEntries[ii]->getLFHOffset();
+            if (nextOffset == 0)
+                nextOffset = mEOCD.mCentralDirOffset;
+            span = nextOffset - pEntry->getLFHOffset();
+
+            assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
+        } else {
+            /* This is a directory entry.  It doesn't have
+             * any actual file contents, so there's no need to
+             * move anything.
+             */
+            span = 0;
+        }
+
+        //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
+        //    i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
+
+        if (pEntry->getDeleted()) {
+            adjust += span;
+            delCount++;
+
+            delete pEntry;
+            mEntries.removeAt(i);
+
+            /* adjust loop control */
+            count--;
+            i--;
+        } else if (span != 0 && adjust > 0) {
+            /* shuffle this entry back */
+            //printf("+++ Shuffling '%s' back %ld\n",
+            //    pEntry->getFileName(), adjust);
+            result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
+                        pEntry->getLFHOffset(), span);
+            if (result != NO_ERROR) {
+                /* this is why you use a temp file */
+                ALOGE("error during crunch - archive is toast\n");
+                return result;
+            }
+
+            pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
+        }
+    }
+
+    /*
+     * Fix EOCD info.  We have to wait until the end to do some of this
+     * because we use mCentralDirOffset to determine "span" for the
+     * last entry.
+     */
+    mEOCD.mCentralDirOffset -= adjust;
+    mEOCD.mNumEntries -= delCount;
+    mEOCD.mTotalNumEntries -= delCount;
+    mEOCD.mCentralDirSize = 0;  // mark invalid; set by flush()
+
+    assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
+    assert(mEOCD.mNumEntries == count);
+
+    return result;
+}
+
+/*
+ * Works like memmove(), but on pieces of a file.
+ */
+status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
+{
+    if (dst == src || n <= 0)
+        return NO_ERROR;
+
+    unsigned char readBuf[32768];
+
+    if (dst < src) {
+        /* shift stuff toward start of file; must read from start */
+        while (n != 0) {
+            size_t getSize = sizeof(readBuf);
+            if (getSize > n)
+                getSize = n;
+
+            if (fseek(fp, (long) src, SEEK_SET) != 0) {
+                ALOGD("filemove src seek %ld failed\n", (long) src);
+                return UNKNOWN_ERROR;
+            }
+
+            if (fread(readBuf, 1, getSize, fp) != getSize) {
+                ALOGD("filemove read %ld off=%ld failed\n",
+                    (long) getSize, (long) src);
+                return UNKNOWN_ERROR;
+            }
+
+            if (fseek(fp, (long) dst, SEEK_SET) != 0) {
+                ALOGD("filemove dst seek %ld failed\n", (long) dst);
+                return UNKNOWN_ERROR;
+            }
+
+            if (fwrite(readBuf, 1, getSize, fp) != getSize) {
+                ALOGD("filemove write %ld off=%ld failed\n",
+                    (long) getSize, (long) dst);
+                return UNKNOWN_ERROR;
+            }
+
+            src += getSize;
+            dst += getSize;
+            n -= getSize;
+        }
+    } else {
+        /* shift stuff toward end of file; must read from end */
+        assert(false);      // write this someday, maybe
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+
+/*
+ * Get the modification time from a file descriptor.
+ */
+time_t ZipFile::getModTime(int fd)
+{
+    struct stat sb;
+
+    if (fstat(fd, &sb) < 0) {
+        ALOGD("HEY: fstat on fd %d failed\n", fd);
+        return (time_t) -1;
+    }
+
+    return sb.st_mtime;
+}
+
+
+#if 0       /* this is a bad idea */
+/*
+ * Get a copy of the Zip file descriptor.
+ *
+ * We don't allow this if the file was opened read-write because we tend
+ * to leave the file contents in an uncertain state between calls to
+ * flush().  The duplicated file descriptor should only be valid for reads.
+ */
+int ZipFile::getZipFd(void) const
+{
+    if (!mReadOnly)
+        return INVALID_OPERATION;
+    assert(mZipFp != NULL);
+
+    int fd;
+    fd = dup(fileno(mZipFp));
+    if (fd < 0) {
+        ALOGD("didn't work, errno=%d\n", errno);
+    }
+
+    return fd;
+}
+#endif
+
+
+#if 0
+/*
+ * Expand data.
+ */
+bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
+{
+    return false;
+}
+#endif
+
+// free the memory when you're done
+void* ZipFile::uncompress(const ZipEntry* entry)
+{
+    size_t unlen = entry->getUncompressedLen();
+    size_t clen = entry->getCompressedLen();
+
+    void* buf = malloc(unlen);
+    if (buf == NULL) {
+        return NULL;
+    }
+
+    fseek(mZipFp, 0, SEEK_SET);
+
+    off_t offset = entry->getFileOffset();
+    if (fseek(mZipFp, offset, SEEK_SET) != 0) {
+        goto bail;
+    }
+
+    switch (entry->getCompressionMethod())
+    {
+        case ZipEntry::kCompressStored: {
+            ssize_t amt = fread(buf, 1, unlen, mZipFp);
+            if (amt != (ssize_t)unlen) {
+                goto bail;
+            }
+#if 0
+            printf("data...\n");
+            const unsigned char* p = (unsigned char*)buf;
+            const unsigned char* end = p+unlen;
+            for (int i=0; i<32 && p < end; i++) {
+                printf("0x%08x ", (int)(offset+(i*0x10)));
+                for (int j=0; j<0x10 && p < end; j++) {
+                    printf(" %02x", *p);
+                    p++;
+                }
+                printf("\n");
+            }
+#endif
+
+            }
+            break;
+        case ZipEntry::kCompressDeflated: {
+            if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
+                goto bail;
+            }
+            }
+            break;
+        default:
+            goto bail;
+    }
+    return buf;
+
+bail:
+    free(buf);
+    return NULL;
+}
+
+
+/*
+ * ===========================================================================
+ *      ZipFile::EndOfCentralDir
+ * ===========================================================================
+ */
+
+/*
+ * Read the end-of-central-dir fields.
+ *
+ * "buf" should be positioned at the EOCD signature, and should contain
+ * the entire EOCD area including the comment.
+ */
+status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
+{
+    /* don't allow re-use */
+    assert(mComment == NULL);
+
+    if (len < kEOCDLen) {
+        /* looks like ZIP file got truncated */
+        ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
+            kEOCDLen, len);
+        return INVALID_OPERATION;
+    }
+
+    /* this should probably be an assert() */
+    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
+        return UNKNOWN_ERROR;
+
+    mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
+    mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
+    mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
+    mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
+    mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
+    mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
+    mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
+
+    // TODO: validate mCentralDirOffset
+
+    if (mCommentLen > 0) {
+        if (kEOCDLen + mCommentLen > len) {
+            ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
+                kEOCDLen, mCommentLen, len);
+            return UNKNOWN_ERROR;
+        }
+        mComment = new unsigned char[mCommentLen];
+        memcpy(mComment, buf + kEOCDLen, mCommentLen);
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Write an end-of-central-directory section.
+ */
+status_t ZipFile::EndOfCentralDir::write(FILE* fp)
+{
+    unsigned char buf[kEOCDLen];
+
+    ZipEntry::putLongLE(&buf[0x00], kSignature);
+    ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
+    ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
+    ZipEntry::putShortLE(&buf[0x08], mNumEntries);
+    ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
+    ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
+    ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
+    ZipEntry::putShortLE(&buf[0x14], mCommentLen);
+
+    if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
+        return UNKNOWN_ERROR;
+    if (mCommentLen > 0) {
+        assert(mComment != NULL);
+        if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
+            return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ * Dump the contents of an EndOfCentralDir object.
+ */
+void ZipFile::EndOfCentralDir::dump(void) const
+{
+    ALOGD(" EndOfCentralDir contents:\n");
+    ALOGD("  diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
+        mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
+    ALOGD("  centDirSize=%lu centDirOff=%lu commentLen=%u\n",
+        mCentralDirSize, mCentralDirOffset, mCommentLen);
+}
+
diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h
new file mode 100644
index 0000000..7877550
--- /dev/null
+++ b/tools/aapt/ZipFile.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// General-purpose Zip archive access.  This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include <utils/Vector.h>
+#include <utils/Errors.h>
+#include <stdio.h>
+
+#include "ZipEntry.h"
+
+namespace android {
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes.  Because we're only interested
+ * in using this for packaging, we don't worry about such things.  Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+    ZipFile(void)
+      : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+      {}
+    ~ZipFile(void) {
+        if (!mReadOnly)
+            flush();
+        if (mZipFp != NULL)
+            fclose(mZipFp);
+        discardEntries();
+    }
+
+    /*
+     * Open a new or existing archive.
+     */
+    enum {
+        kOpenReadOnly   = 0x01,
+        kOpenReadWrite  = 0x02,
+        kOpenCreate     = 0x04,     // create if it doesn't exist
+        kOpenTruncate   = 0x08,     // if it exists, empty it
+    };
+    status_t open(const char* zipFileName, int flags);
+
+    /*
+     * Add a file to the end of the archive.  Specify whether you want the
+     * library to try to store it compressed.
+     *
+     * If "storageName" is specified, the archive will use that instead
+     * of "fileName".
+     *
+     * If there is already an entry with the same name, the call fails.
+     * Existing entries with the same name must be removed first.
+     *
+     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+     */
+    status_t add(const char* fileName, int compressionMethod,
+        ZipEntry** ppEntry)
+    {
+        return add(fileName, fileName, compressionMethod, ppEntry);
+    }
+    status_t add(const char* fileName, const char* storageName,
+        int compressionMethod, ZipEntry** ppEntry)
+    {
+        return addCommon(fileName, NULL, 0, storageName,
+                         ZipEntry::kCompressStored,
+                         compressionMethod, ppEntry);
+    }
+
+    /*
+     * Add a file that is already compressed with gzip.
+     *
+     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+     */
+    status_t addGzip(const char* fileName, const char* storageName,
+        ZipEntry** ppEntry)
+    {
+        return addCommon(fileName, NULL, 0, storageName,
+                         ZipEntry::kCompressDeflated,
+                         ZipEntry::kCompressDeflated, ppEntry);
+    }
+
+    /*
+     * Add a file from an in-memory data buffer.
+     *
+     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+     */
+    status_t add(const void* data, size_t size, const char* storageName,
+        int compressionMethod, ZipEntry** ppEntry)
+    {
+        return addCommon(NULL, data, size, storageName,
+                         ZipEntry::kCompressStored,
+                         compressionMethod, ppEntry);
+    }
+
+    /*
+     * Add an entry by copying it from another zip file.  If "padding" is
+     * nonzero, the specified number of bytes will be added to the "extra"
+     * field in the header.
+     *
+     * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+     */
+    status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+        int padding, ZipEntry** ppEntry);
+
+    /*
+     * Mark an entry as having been removed.  It is not actually deleted
+     * from the archive or our internal data structures until flush() is
+     * called.
+     */
+    status_t remove(ZipEntry* pEntry);
+
+    /*
+     * Flush changes.  If mNeedCDRewrite is set, this writes the central dir.
+     */
+    status_t flush(void);
+
+    /*
+     * Expand the data into the buffer provided.  The buffer must hold
+     * at least <uncompressed len> bytes.  Variation expands directly
+     * to a file.
+     *
+     * Returns "false" if an error was encountered in the compressed data.
+     */
+    //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+    //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+    void* uncompress(const ZipEntry* pEntry);
+
+    /*
+     * Get an entry, by name.  Returns NULL if not found.
+     *
+     * Does not return entries pending deletion.
+     */
+    ZipEntry* getEntryByName(const char* fileName) const;
+
+    /*
+     * Get the Nth entry in the archive.
+     *
+     * This will return an entry that is pending deletion.
+     */
+    int getNumEntries(void) const { return mEntries.size(); }
+    ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+    /* these are private and not defined */
+    ZipFile(const ZipFile& src);
+    ZipFile& operator=(const ZipFile& src);
+
+    class EndOfCentralDir {
+    public:
+        EndOfCentralDir(void) :
+            mDiskNumber(0),
+            mDiskWithCentralDir(0),
+            mNumEntries(0),
+            mTotalNumEntries(0),
+            mCentralDirSize(0),
+            mCentralDirOffset(0),
+            mCommentLen(0),
+            mComment(NULL)
+            {}
+        virtual ~EndOfCentralDir(void) {
+            delete[] mComment;
+        }
+
+        status_t readBuf(const unsigned char* buf, int len);
+        status_t write(FILE* fp);
+
+        //unsigned long   mSignature;
+        unsigned short  mDiskNumber;
+        unsigned short  mDiskWithCentralDir;
+        unsigned short  mNumEntries;
+        unsigned short  mTotalNumEntries;
+        unsigned long   mCentralDirSize;
+        unsigned long   mCentralDirOffset;      // offset from first disk
+        unsigned short  mCommentLen;
+        unsigned char*  mComment;
+
+        enum {
+            kSignature      = 0x06054b50,
+            kEOCDLen        = 22,       // EndOfCentralDir len, excl. comment
+
+            kMaxCommentLen  = 65535,    // longest possible in ushort
+            kMaxEOCDSearch  = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+        };
+
+        void dump(void) const;
+    };
+
+
+    /* read all entries in the central dir */
+    status_t readCentralDir(void);
+
+    /* crunch deleted entries out */
+    status_t crunchArchive(void);
+
+    /* clean up mEntries */
+    void discardEntries(void);
+
+    /* common handler for all "add" functions */
+    status_t addCommon(const char* fileName, const void* data, size_t size,
+        const char* storageName, int sourceType, int compressionMethod,
+        ZipEntry** ppEntry);
+
+    /* copy all of "srcFp" into "dstFp" */
+    status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+    /* copy all of "data" into "dstFp" */
+    status_t copyDataToFp(FILE* dstFp,
+        const void* data, size_t size, unsigned long* pCRC32);
+    /* copy some of "srcFp" into "dstFp" */
+    status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+        unsigned long* pCRC32);
+    /* like memmove(), but on parts of a single file */
+    status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+    /* compress all of "srcFp" into "dstFp", using Deflate */
+    status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+        const void* data, size_t size, unsigned long* pCRC32);
+
+    /* get modification date from a file descriptor */
+    time_t getModTime(int fd);
+
+    /*
+     * We use stdio FILE*, which gives us buffering but makes dealing
+     * with files >2GB awkward.  Until we support Zip64, we're fine.
+     */
+    FILE*           mZipFp;             // Zip file pointer
+
+    /* one of these per file */
+    EndOfCentralDir mEOCD;
+
+    /* did we open this read-only? */
+    bool            mReadOnly;
+
+    /* set this when we trash the central dir */
+    bool            mNeedCDRewrite;
+
+    /*
+     * One ZipEntry per entry in the zip file.  I'm using pointers instead
+     * of objects because it's easier than making operator= work for the
+     * classes and sub-classes.
+     */
+    Vector<ZipEntry*>   mEntries;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt/printapk.cpp b/tools/aapt/printapk.cpp
new file mode 100644
index 0000000..4cf73d8
--- /dev/null
+++ b/tools/aapt/printapk.cpp
@@ -0,0 +1,127 @@
+#include <utils/ResourceTypes.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include <zipfile/zipfile.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+using namespace android;
+
+static int
+usage()
+{
+    fprintf(stderr,
+            "usage: apk APKFILE\n"
+            "\n"
+            "APKFILE   an android packge file produced by aapt.\n"
+            );
+    return 1;
+}
+
+
+int
+main(int argc, char** argv)
+{
+    const char* filename;
+    int fd;
+    ssize_t amt;
+    off_t size;
+    void* buf;
+    zipfile_t zip;
+    zipentry_t entry;
+    void* cookie;
+    void* resfile;
+    int bufsize;
+    int err;
+
+    if (argc != 2) {
+        return usage();
+    }
+
+    filename = argv[1];
+    fd = open(filename, O_RDONLY);
+    if (fd == -1) {
+        fprintf(stderr, "apk: couldn't open file for read: %s\n", filename);
+        return 1;
+    }
+
+    size = lseek(fd, 0, SEEK_END);
+    amt = lseek(fd, 0, SEEK_SET);
+
+    if (size < 0 || amt < 0) {
+        fprintf(stderr, "apk: error determining file size: %s\n", filename);
+        return 1;
+    }
+
+    buf = malloc(size);
+    if (buf == NULL) {
+        fprintf(stderr, "apk: file too big: %s\n", filename);
+        return 1;
+    }
+
+    amt = read(fd, buf, size);
+    if (amt != size) {
+        fprintf(stderr, "apk: error reading file: %s\n", filename);
+        return 1;
+    }
+
+    close(fd);
+
+    zip = init_zipfile(buf, size);
+    if (zip == NULL) {
+        fprintf(stderr, "apk: file doesn't seem to be a zip file: %s\n",
+                filename);
+        return 1;
+    }
+
+    printf("files:\n");
+    cookie = NULL;
+    while ((entry = iterate_zipfile(zip, &cookie))) {
+        char* name = get_zipentry_name(entry);
+        printf("  %s\n", name);
+        free(name);
+    }
+
+    entry = lookup_zipentry(zip, "resources.arsc");
+    if (entry != NULL) {
+        size = get_zipentry_size(entry);
+        bufsize = size + (size / 1000) + 1;
+        resfile = malloc(bufsize);
+
+        err = decompress_zipentry(entry, resfile, bufsize);
+        if (err != 0) {
+            fprintf(stderr, "apk: error decompressing resources.arsc");
+            return 1;
+        }
+
+        ResTable res(resfile, size, resfile);
+        res.print();
+#if 0
+        size_t tableCount = res.getTableCount();
+        printf("Tables: %d\n", (int)tableCount);
+        for (size_t tableIndex=0; tableIndex<tableCount; tableIndex++) {
+            const ResStringPool* strings = res.getTableStringBlock(tableIndex);
+            size_t stringCount = strings->size();
+            for (size_t stringIndex=0; stringIndex<stringCount; stringIndex++) {
+                size_t len;
+                const char16_t* ch = strings->stringAt(stringIndex, &len);
+                String8 s(String16(ch, len));
+                printf("  [%3d] %s\n", (int)stringIndex, s.string());
+            }
+        }
+
+        size_t basePackageCount = res.getBasePackageCount();
+        printf("Base Packages: %d\n", (int)basePackageCount);
+        for (size_t bpIndex=0; bpIndex<basePackageCount; bpIndex++) {
+            const char16_t* ch = res.getBasePackageName(bpIndex);
+            String8 s = String8(String16(ch));
+            printf("  [%3d] %s\n", (int)bpIndex, s.string());
+        }
+#endif
+    }
+
+
+    return 0;
+}
diff --git a/tools/aapt/pseudolocalize.cpp b/tools/aapt/pseudolocalize.cpp
new file mode 100644
index 0000000..9e50c5a
--- /dev/null
+++ b/tools/aapt/pseudolocalize.cpp
@@ -0,0 +1,119 @@
+#include "pseudolocalize.h"
+
+using namespace std;
+
+static const char*
+pseudolocalize_char(char c)
+{
+    switch (c) {
+        case 'a':   return "\xc4\x83";
+        case 'b':   return "\xcf\x84";
+        case 'c':   return "\xc4\x8b";
+        case 'd':   return "\xc4\x8f";
+        case 'e':   return "\xc4\x99";
+        case 'f':   return "\xc6\x92";
+        case 'g':   return "\xc4\x9d";
+        case 'h':   return "\xd1\x9b";
+        case 'i':   return "\xcf\x8a";
+        case 'j':   return "\xc4\xb5";
+        case 'k':   return "\xc4\xb8";
+        case 'l':   return "\xc4\xba";
+        case 'm':   return "\xe1\xb8\xbf";
+        case 'n':   return "\xd0\xb8";
+        case 'o':   return "\xcf\x8c";
+        case 'p':   return "\xcf\x81";
+        case 'q':   return "\x51";
+        case 'r':   return "\xd2\x91";
+        case 's':   return "\xc5\xa1";
+        case 't':   return "\xd1\x82";
+        case 'u':   return "\xce\xb0";
+        case 'v':   return "\x56";
+        case 'w':   return "\xe1\xba\x85";
+        case 'x':   return "\xd1\x85";
+        case 'y':   return "\xe1\xbb\xb3";
+        case 'z':   return "\xc5\xba";
+        case 'A':   return "\xc3\x85";
+        case 'B':   return "\xce\xb2";
+        case 'C':   return "\xc4\x88";
+        case 'D':   return "\xc4\x90";
+        case 'E':   return "\xd0\x84";
+        case 'F':   return "\xce\x93";
+        case 'G':   return "\xc4\x9e";
+        case 'H':   return "\xc4\xa6";
+        case 'I':   return "\xd0\x87";
+        case 'J':   return "\xc4\xb5";
+        case 'K':   return "\xc4\xb6";
+        case 'L':   return "\xc5\x81";
+        case 'M':   return "\xe1\xb8\xbe";
+        case 'N':   return "\xc5\x83";
+        case 'O':   return "\xce\x98";
+        case 'P':   return "\xcf\x81";
+        case 'Q':   return "\x71";
+        case 'R':   return "\xd0\xaf";
+        case 'S':   return "\xc8\x98";
+        case 'T':   return "\xc5\xa6";
+        case 'U':   return "\xc5\xa8";
+        case 'V':   return "\xce\xbd";
+        case 'W':   return "\xe1\xba\x84";
+        case 'X':   return "\xc3\x97";
+        case 'Y':   return "\xc2\xa5";
+        case 'Z':   return "\xc5\xbd";
+        default:    return NULL;
+    }
+}
+
+/**
+ * Converts characters so they look like they've been localized.
+ *
+ * Note: This leaves escape sequences untouched so they can later be
+ * processed by ResTable::collectString in the normal way.
+ */
+string
+pseudolocalize_string(const string& source)
+{
+    const char* s = source.c_str();
+    string result;
+    const size_t I = source.length();
+    for (size_t i=0; i<I; i++) {
+        char c = s[i];
+        if (c == '\\') {
+            if (i<I-1) {
+                result += '\\';
+                i++;
+                c = s[i];
+                switch (c) {
+                    case 'u':
+                        // this one takes up 5 chars
+                        result += string(s+i, 5);
+                        i += 4;
+                        break;
+                    case 't':
+                    case 'n':
+                    case '#':
+                    case '@':
+                    case '?':
+                    case '"':
+                    case '\'':
+                    case '\\':
+                    default:
+                        result += c;
+                        break;
+                }
+            } else {
+                result += c;
+            }
+        } else {
+            const char* p = pseudolocalize_char(c);
+            if (p != NULL) {
+                result += p;
+            } else {
+                result += c;
+            }
+        }
+    }
+
+    //printf("result=\'%s\'\n", result.c_str());
+    return result;
+}
+
+
diff --git a/tools/aapt/pseudolocalize.h b/tools/aapt/pseudolocalize.h
new file mode 100644
index 0000000..94cb034
--- /dev/null
+++ b/tools/aapt/pseudolocalize.h
@@ -0,0 +1,9 @@
+#ifndef HOST_PSEUDOLOCALIZE_H
+#define HOST_PSEUDOLOCALIZE_H
+
+#include <string>
+
+std::string pseudolocalize_string(const std::string& source);
+
+#endif // HOST_PSEUDOLOCALIZE_H
+
diff --git a/tools/aapt/qsort_r_compat.c b/tools/aapt/qsort_r_compat.c
new file mode 100644
index 0000000..2a8dbe8
--- /dev/null
+++ b/tools/aapt/qsort_r_compat.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "qsort_r_compat.h"
+
+/*
+ * Note: This code is only used on the host, and is primarily here for
+ * Mac OS compatibility. Apparently, glibc and Apple's libc disagree on
+ * the parameter order for qsort_r.
+ */
+
+#if HAVE_BSD_QSORT_R
+
+/*
+ * BSD qsort_r parameter order is as we have defined here.
+ */
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+        int (*compar)(void*, const void* , const void*)) {
+    qsort_r(base, nel, width, thunk, compar);
+}
+
+#elif HAVE_GNU_QSORT_R
+
+/*
+ * GNU qsort_r parameter order places the thunk parameter last.
+ */
+
+struct compar_data {
+    void* thunk;
+    int (*compar)(void*, const void* , const void*);
+};
+
+static int compar_wrapper(const void* a, const void* b, void* data) {
+    struct compar_data* compar_data = (struct compar_data*)data;
+    return compar_data->compar(compar_data->thunk, a, b);
+}
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+        int (*compar)(void*, const void* , const void*)) {
+    struct compar_data compar_data;
+    compar_data.thunk = thunk;
+    compar_data.compar = compar;
+    qsort_r(base, nel, width, compar_wrapper, &compar_data);
+}
+
+#else
+
+/*
+ * Emulate qsort_r using thread local storage to access the thunk data.
+ */
+
+#include <cutils/threads.h>
+
+static thread_store_t compar_data_key = THREAD_STORE_INITIALIZER;
+
+struct compar_data {
+    void* thunk;
+    int (*compar)(void*, const void* , const void*);
+};
+
+static int compar_wrapper(const void* a, const void* b) {
+    struct compar_data* compar_data = (struct compar_data*)thread_store_get(&compar_data_key);
+    return compar_data->compar(compar_data->thunk, a, b);
+}
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+        int (*compar)(void*, const void* , const void*)) {
+    struct compar_data compar_data;
+    compar_data.thunk = thunk;
+    compar_data.compar = compar;
+    thread_store_set(&compar_data_key, &compar_data, NULL);
+    qsort(base, nel, width, compar_wrapper);
+}
+
+#endif
diff --git a/tools/aapt/qsort_r_compat.h b/tools/aapt/qsort_r_compat.h
new file mode 100644
index 0000000..e14f999
--- /dev/null
+++ b/tools/aapt/qsort_r_compat.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Provides a portable version of qsort_r, called qsort_r_compat, which is a
+ * reentrant variant of qsort that passes a user data pointer to its comparator.
+ * This implementation follows the BSD parameter convention.
+ */
+
+#ifndef ___QSORT_R_COMPAT_H
+#define ___QSORT_R_COMPAT_H
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+        int (*compar)(void*, const void* , const void* ));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ___QSORT_R_COMPAT_H
diff --git a/tools/aapt/tests/CrunchCache_test.cpp b/tools/aapt/tests/CrunchCache_test.cpp
new file mode 100644
index 0000000..20b5022
--- /dev/null
+++ b/tools/aapt/tests/CrunchCache_test.cpp
@@ -0,0 +1,97 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#include <utils/String8.h>
+#include <iostream>
+#include <errno.h>
+
+#include "CrunchCache.h"
+#include "FileFinder.h"
+#include "MockFileFinder.h"
+#include "CacheUpdater.h"
+#include "MockCacheUpdater.h"
+
+using namespace android;
+using std::cout;
+using std::endl;
+
+void expectEqual(int got, int expected, const char* desc) {
+    cout << "Checking " << desc << ": ";
+    cout << "Got " << got << ", expected " << expected << "...";
+    cout << ( (got == expected) ? "PASSED" : "FAILED") << endl;
+    errno += ((got == expected) ? 0 : 1);
+}
+
+int main() {
+
+    errno = 0;
+
+    String8 source("res");
+    String8 dest("res2");
+
+    // Create data for MockFileFinder to feed to the cache
+    KeyedVector<String8, time_t> sourceData;
+    // This shouldn't be updated
+    sourceData.add(String8("res/drawable/hello.png"),3);
+    // This should be updated
+    sourceData.add(String8("res/drawable/world.png"),5);
+    // This should cause make directory to be called
+    sourceData.add(String8("res/drawable-cool/hello.png"),3);
+
+    KeyedVector<String8, time_t> destData;
+    destData.add(String8("res2/drawable/hello.png"),3);
+    destData.add(String8("res2/drawable/world.png"),3);
+    // this should call delete
+    destData.add(String8("res2/drawable/dead.png"),3);
+
+    // Package up data and create mock file finder
+    KeyedVector<String8, KeyedVector<String8,time_t> > data;
+    data.add(source,sourceData);
+    data.add(dest,destData);
+    FileFinder* ff = new MockFileFinder(data);
+    CrunchCache cc(source,dest,ff);
+
+    MockCacheUpdater* mcu = new MockCacheUpdater();
+    CacheUpdater* cu(mcu);
+
+    cout << "Running Crunch...";
+    int result = cc.crunch(cu);
+    cout << ((result > 0) ? "PASSED" : "FAILED") << endl;
+    errno += ((result > 0) ? 0 : 1);
+
+    const int EXPECTED_RESULT = 2;
+    expectEqual(result, EXPECTED_RESULT, "number of files touched");
+
+    cout << "Checking calls to deleteFile and processImage:" << endl;
+    const int EXPECTED_DELETES = 1;
+    const int EXPECTED_PROCESSED = 2;
+    // Deletes
+    expectEqual(mcu->deleteCount, EXPECTED_DELETES, "deleteFile");
+    // processImage
+    expectEqual(mcu->processCount, EXPECTED_PROCESSED, "processImage");
+
+    const int EXPECTED_OVERWRITES = 3;
+    result = cc.crunch(cu, true);
+    expectEqual(result, EXPECTED_OVERWRITES, "number of files touched with overwrite");
+    \
+
+    if (errno == 0)
+        cout << "ALL TESTS PASSED!" << endl;
+    else
+        cout << errno << " TESTS FAILED" << endl;
+
+    delete ff;
+    delete cu;
+
+    // TESTS BELOW WILL GO AWAY SOON
+
+    String8 source2("ApiDemos/res");
+    String8 dest2("ApiDemos/res2");
+
+    FileFinder* sff = new SystemFileFinder();
+    CacheUpdater* scu = new SystemCacheUpdater();
+
+    CrunchCache scc(source2,dest2,sff);
+
+    scc.crunch(scu);
+}
\ No newline at end of file
diff --git a/tools/aapt/tests/FileFinder_test.cpp b/tools/aapt/tests/FileFinder_test.cpp
new file mode 100644
index 0000000..07bd665
--- /dev/null
+++ b/tools/aapt/tests/FileFinder_test.cpp
@@ -0,0 +1,101 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <iostream>
+#include <cassert>
+#include <utils/String8.h>
+#include <utility>
+
+#include "DirectoryWalker.h"
+#include "MockDirectoryWalker.h"
+#include "FileFinder.h"
+
+using namespace android;
+
+using std::pair;
+using std::cout;
+using std::endl;
+
+
+
+int main()
+{
+
+    cout << "\n\n STARTING FILE FINDER TESTS" << endl;
+    String8 path("ApiDemos");
+
+    // Storage to pass to findFiles()
+    KeyedVector<String8,time_t> testStorage;
+
+    // Mock Directory Walker initialization. First data, then sdw
+    Vector< pair<String8,time_t> > data;
+    data.push( pair<String8,time_t>(String8("hello.png"),3) );
+    data.push( pair<String8,time_t>(String8("world.PNG"),3) );
+    data.push( pair<String8,time_t>(String8("foo.pNg"),3) );
+    // Neither of these should be found
+    data.push( pair<String8,time_t>(String8("hello.jpg"),3) );
+    data.push( pair<String8,time_t>(String8(".hidden.png"),3));
+
+    DirectoryWalker* sdw = new StringDirectoryWalker(path,data);
+
+    // Extensions to look for
+    Vector<String8> exts;
+    exts.push(String8(".png"));
+
+    errno = 0;
+
+    // Make sure we get a valid mock directory walker
+    // Make sure we finish without errors
+    cout << "Checking DirectoryWalker...";
+    assert(sdw != NULL);
+    cout << "PASSED" << endl;
+
+    // Make sure we finish without errors
+    cout << "Running findFiles()...";
+    bool findStatus = FileFinder::findFiles(path,exts, testStorage, sdw);
+    assert(findStatus);
+    cout << "PASSED" << endl;
+
+    const size_t SIZE_EXPECTED = 3;
+    // Check to make sure we have the right number of things in our storage
+    cout << "Running size comparison: Size is " << testStorage.size() << ", ";
+    cout << "Expected " << SIZE_EXPECTED << "...";
+    if(testStorage.size() == SIZE_EXPECTED)
+        cout << "PASSED" << endl;
+    else {
+        cout << "FAILED" << endl;
+        errno++;
+    }
+
+    // Check to make sure that each of our found items has the right extension
+    cout << "Checking Returned Extensions...";
+    bool extsOkay = true;
+    String8 wrongExts;
+    for (size_t i = 0; i < SIZE_EXPECTED; ++i) {
+        String8 testExt(testStorage.keyAt(i).getPathExtension());
+        testExt.toLower();
+        if (testExt != ".png") {
+            wrongExts += testStorage.keyAt(i);
+            wrongExts += "\n";
+            extsOkay = false;
+        }
+    }
+    if (extsOkay)
+        cout << "PASSED" << endl;
+    else {
+        cout << "FAILED" << endl;
+        cout << "The following extensions didn't check out" << endl << wrongExts;
+    }
+
+    // Clean up
+    delete sdw;
+
+    if(errno == 0) {
+        cout << "ALL TESTS PASSED" << endl;
+    } else {
+        cout << errno << " TESTS FAILED" << endl;
+    }
+    return errno;
+}
\ No newline at end of file
diff --git a/tools/aapt/tests/MockCacheUpdater.h b/tools/aapt/tests/MockCacheUpdater.h
new file mode 100644
index 0000000..c7f4bd7
--- /dev/null
+++ b/tools/aapt/tests/MockCacheUpdater.h
@@ -0,0 +1,40 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#ifndef MOCKCACHEUPDATER_H
+#define MOCKCACHEUPDATER_H
+
+#include <utils/String8.h>
+#include "CacheUpdater.h"
+
+using namespace android;
+
+class MockCacheUpdater : public CacheUpdater {
+public:
+
+    MockCacheUpdater()
+        : deleteCount(0), processCount(0) { };
+
+    // Make sure all the directories along this path exist
+    virtual void ensureDirectoriesExist(String8 path)
+    {
+        // Nothing to do
+    };
+
+    // Delete a file
+    virtual void deleteFile(String8 path) {
+        deleteCount++;
+    };
+
+    // Process an image from source out to dest
+    virtual void processImage(String8 source, String8 dest) {
+        processCount++;
+    };
+
+    // DATA MEMBERS
+    int deleteCount;
+    int processCount;
+private:
+};
+
+#endif // MOCKCACHEUPDATER_H
\ No newline at end of file
diff --git a/tools/aapt/tests/MockDirectoryWalker.h b/tools/aapt/tests/MockDirectoryWalker.h
new file mode 100644
index 0000000..5900cf3
--- /dev/null
+++ b/tools/aapt/tests/MockDirectoryWalker.h
@@ -0,0 +1,85 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#ifndef MOCKDIRECTORYWALKER_H
+#define MOCKDIRECTORYWALKER_H
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+#include <utility>
+#include "DirectoryWalker.h"
+
+using namespace android;
+using std::pair;
+
+// String8 Directory Walker
+// This is an implementation of the Directory Walker abstraction that is built
+// for testing.
+// Instead of system calls it queries a private data structure for the directory
+// entries. It takes a path and a map of filenames and their modification times.
+// functions are inlined since they are short and simple
+
+class StringDirectoryWalker : public DirectoryWalker {
+public:
+    StringDirectoryWalker(String8& path, Vector< pair<String8,time_t> >& data)
+        :  mPos(0), mBasePath(path), mData(data) {
+        //fprintf(stdout,"StringDW built to mimic %s with %d files\n",
+        //       mBasePath.string());
+    };
+    // Default copy constructor, and destructor are fine
+
+    virtual bool openDir(String8 path) {
+        // If the user is trying to query the "directory" that this
+        // walker was initialized with, then return success. Else fail.
+        return path == mBasePath;
+    };
+    virtual bool openDir(const char* path) {
+        String8 p(path);
+        openDir(p);
+        return true;
+    };
+    // Advance to next entry in the Vector
+    virtual struct dirent* nextEntry() {
+        // Advance position and check to see if we're done
+        if (mPos >= mData.size())
+            return NULL;
+
+        // Place data in the entry descriptor. This class only returns files.
+        mEntry.d_type = DT_REG;
+        mEntry.d_ino = mPos;
+        // Copy chars from the string name to the entry name
+        size_t i = 0;
+        for (i; i < mData[mPos].first.size(); ++i)
+            mEntry.d_name[i] = mData[mPos].first[i];
+        mEntry.d_name[i] = '\0';
+
+        // Place data in stats
+        mStats.st_ino = mPos;
+        mStats.st_mtime = mData[mPos].second;
+
+        // Get ready to move to the next entry
+        mPos++;
+
+        return &mEntry;
+    };
+    // Get the stats for the current entry
+    virtual struct stat*   entryStats() {
+        return &mStats;
+    };
+    // Nothing to do in clean up
+    virtual void closeDir() {
+        // Nothing to do
+    };
+    virtual DirectoryWalker* clone() {
+        return new StringDirectoryWalker(*this);
+    };
+private:
+    // Current position in the Vector
+    size_t mPos;
+    // Base path
+    String8 mBasePath;
+    // Data to simulate a directory full of files.
+    Vector< pair<String8,time_t> > mData;
+};
+
+#endif // MOCKDIRECTORYWALKER_H
\ No newline at end of file
diff --git a/tools/aapt/tests/MockFileFinder.h b/tools/aapt/tests/MockFileFinder.h
new file mode 100644
index 0000000..da5ea4f
--- /dev/null
+++ b/tools/aapt/tests/MockFileFinder.h
@@ -0,0 +1,55 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+#ifndef MOCKFILEFINDER_H
+#define MOCKFILEFINDER_H
+
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+
+using namespace android;
+
+class MockFileFinder : public FileFinder {
+public:
+    MockFileFinder (KeyedVector<String8, KeyedVector<String8,time_t> >& files)
+        : mFiles(files)
+    {
+        // Nothing left to do
+    };
+
+    /**
+     * findFiles implementation for the abstraction.
+     * PRECONDITIONS:
+     *  No checking is done, so there MUST be an entry in mFiles with
+     *  path matching basePath.
+     *
+     * POSTCONDITIONS:
+     *  fileStore is filled with a copy of the data in mFiles corresponding
+     *  to the basePath.
+     */
+
+    virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+                           KeyedVector<String8,time_t>& fileStore,
+                           DirectoryWalker* dw)
+    {
+        const KeyedVector<String8,time_t>* payload(&mFiles.valueFor(basePath));
+        // Since KeyedVector doesn't implement swap
+        // (who doesn't use swap??) we loop and add one at a time.
+        for (size_t i = 0; i < payload->size(); ++i) {
+            fileStore.add(payload->keyAt(i),payload->valueAt(i));
+        }
+        return true;
+    }
+
+private:
+    // Virtual mapping between "directories" and the "files" contained
+    // in them
+    KeyedVector<String8, KeyedVector<String8,time_t> > mFiles;
+};
+
+
+#endif // MOCKFILEFINDER_H
\ No newline at end of file
diff --git a/tools/aapt/tests/plurals/AndroidManifest.xml b/tools/aapt/tests/plurals/AndroidManifest.xml
new file mode 100644
index 0000000..c721dee
--- /dev/null
+++ b/tools/aapt/tests/plurals/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.aapt.test.plurals">
+
+</manifest>
diff --git a/tools/aapt/tests/plurals/res/values/strings.xml b/tools/aapt/tests/plurals/res/values/strings.xml
new file mode 100644
index 0000000..1c1fc19
--- /dev/null
+++ b/tools/aapt/tests/plurals/res/values/strings.xml
@@ -0,0 +1,7 @@
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="ok">OK</string>
+    <plurals name="a_plural">
+        <item quantity="one">A dog</item>
+        <item quantity="other">Some dogs</item>
+    </plurals>
+</resources>
diff --git a/tools/aapt/tests/plurals/run.sh b/tools/aapt/tests/plurals/run.sh
new file mode 100755
index 0000000..4d39e10
--- /dev/null
+++ b/tools/aapt/tests/plurals/run.sh
@@ -0,0 +1,16 @@
+TEST_DIR=tools/aapt/tests/plurals
+TEST_OUT_DIR=out/plurals_test
+
+rm -rf $TEST_OUT_DIR
+mkdir -p $TEST_OUT_DIR
+mkdir -p $TEST_OUT_DIR/java
+
+#gdb --args \
+aapt package -v -x -m -z  -J $TEST_OUT_DIR/java -M $TEST_DIR/AndroidManifest.xml \
+             -I out/target/common/obj/APPS/framework-res_intermediates/package-export.apk \
+             -P $TEST_OUT_DIR/public_resources.xml \
+             -S $TEST_DIR/res
+
+echo
+echo "==================== FILES CREATED ==================== "
+find $TEST_OUT_DIR -type f