Add support for building split APKs

Build multiple APKs, each containing a disjoint subset
of configurations. These can then be loaded into the device
AssetManager and should operate as if they were never split.

Use the idea of building multiple sets of files, where each
set represents an APK. An ApkBuilder can place files
in a set based on its configuration, but you can actually
add directly to a set, in the case of the resources.arsc and
generated AndroidManifest.xml for splits.

Change-Id: Ic65d3f0ac1bbd290185695b9971d425c85ab1de3
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index e0dab78..12d5389 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -3,8 +3,10 @@
 //
 
 #include "AaptAssets.h"
-#include "ResourceFilter.h"
+#include "AaptConfig.h"
+#include "AaptUtil.h"
 #include "Main.h"
+#include "ResourceFilter.h"
 
 #include <utils/misc.h>
 #include <utils/SortedVector.h>
@@ -14,7 +16,6 @@
 #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";
@@ -149,24 +150,6 @@
 // =========================================================================
 // =========================================================================
 
-/* static */ void AaptLocaleValue::splitAndLowerCase(const char* const chars,
-        Vector<String8>* parts, const char separator) {
-    const char *p = chars;
-    const char *q;
-    while (NULL != (q = strchr(p, separator))) {
-         String8 val(p, q - p);
-         val.toLower();
-         parts->add(val);
-         p = q+1;
-    }
-
-    if (p < chars + strlen(chars)) {
-        String8 val(p);
-        val.toLower();
-        parts->add(val);
-    }
-}
-
 /* static */
 inline bool isAlpha(const String8& string) {
      const size_t length = string.length();
@@ -230,8 +213,7 @@
 bool AaptLocaleValue::initFromFilterString(const String8& str) {
      // A locale (as specified in the filter) is an underscore separated name such
      // as "en_US", "en_Latn_US", or "en_US_POSIX".
-     Vector<String8> parts;
-     splitAndLowerCase(str.string(), &parts, '_');
+     Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '_');
 
      const int numTags = parts.size();
      bool valid = false;
@@ -301,8 +283,7 @@
     if (part[0] == 'b' && part[1] == '+') {
         // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
         // except that the separator is "+" and not "-".
-        Vector<String8> subtags;
-        AaptLocaleValue::splitAndLowerCase(part.string(), &subtags, '+');
+        Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+');
         subtags.removeItemsAt(0);
         if (subtags.size() == 1) {
             setLanguage(subtags[0]);
@@ -438,1349 +419,46 @@
     }
 }
 
-
-/* static */ bool
-AaptGroupEntry::parseFilterNamePart(const String8& part, int* axis, AxisValue* value)
-{
-    ResTable_config config;
-    memset(&config, 0, sizeof(ResTable_config));
-
-    // IMSI - MCC
-    if (getMccName(part.string(), &config)) {
-        *axis = AXIS_MCC;
-        value->intValue = config.mcc;
-        return true;
-    }
-
-    // IMSI - MNC
-    if (getMncName(part.string(), &config)) {
-        *axis = AXIS_MNC;
-        value->intValue = config.mnc;
-        return true;
-    }
-
-    // locale - language
-    if (value->localeValue.initFromFilterString(part)) {
-        *axis = AXIS_LOCALE;
-        return true;
-    }
-
-    // layout direction
-    if (getLayoutDirectionName(part.string(), &config)) {
-        *axis = AXIS_LAYOUTDIR;
-        value->intValue = (config.screenLayout&ResTable_config::MASK_LAYOUTDIR);
-        return true;
-    }
-
-    // smallest screen dp width
-    if (getSmallestScreenWidthDpName(part.string(), &config)) {
-        *axis = AXIS_SMALLESTSCREENWIDTHDP;
-        value->intValue = config.smallestScreenWidthDp;
-        return true;
-    }
-
-    // screen dp width
-    if (getScreenWidthDpName(part.string(), &config)) {
-        *axis = AXIS_SCREENWIDTHDP;
-        value->intValue = config.screenWidthDp;
-        return true;
-    }
-
-    // screen dp height
-    if (getScreenHeightDpName(part.string(), &config)) {
-        *axis = AXIS_SCREENHEIGHTDP;
-        value->intValue = config.screenHeightDp;
-        return true;
-    }
-
-    // screen layout size
-    if (getScreenLayoutSizeName(part.string(), &config)) {
-        *axis = AXIS_SCREENLAYOUTSIZE;
-        value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENSIZE);
-        return true;
-    }
-
-    // screen layout long
-    if (getScreenLayoutLongName(part.string(), &config)) {
-        *axis = AXIS_SCREENLAYOUTLONG;
-        value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENLONG);
-        return true;
-    }
-
-    // orientation
-    if (getOrientationName(part.string(), &config)) {
-        *axis = AXIS_ORIENTATION;
-        value->intValue = config.orientation;
-        return true;
-    }
-
-    // ui mode type
-    if (getUiModeTypeName(part.string(), &config)) {
-        *axis = AXIS_UIMODETYPE;
-        value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
-        return true;
-    }
-
-    // ui mode night
-    if (getUiModeNightName(part.string(), &config)) {
-        *axis = AXIS_UIMODENIGHT;
-        value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
-        return true;
-    }
-
-    // density
-    if (getDensityName(part.string(), &config)) {
-        *axis = AXIS_DENSITY;
-        value->intValue = config.density;
-        return true;
-    }
-
-    // touchscreen
-    if (getTouchscreenName(part.string(), &config)) {
-        *axis = AXIS_TOUCHSCREEN;
-        value->intValue = config.touchscreen;
-        return true;
-    }
-
-    // keyboard hidden
-    if (getKeysHiddenName(part.string(), &config)) {
-        *axis = AXIS_KEYSHIDDEN;
-        value->intValue = config.inputFlags;
-        return true;
-    }
-
-    // keyboard
-    if (getKeyboardName(part.string(), &config)) {
-        *axis = AXIS_KEYBOARD;
-        value->intValue = config.keyboard;
-        return true;
-    }
-
-    // navigation hidden
-    if (getNavHiddenName(part.string(), &config)) {
-        *axis = AXIS_NAVHIDDEN;
-        value->intValue = config.inputFlags;
-        return 0;
-    }
-
-    // navigation
-    if (getNavigationName(part.string(), &config)) {
-        *axis = AXIS_NAVIGATION;
-        value->intValue = config.navigation;
-        return true;
-    }
-
-    // screen size
-    if (getScreenSizeName(part.string(), &config)) {
-        *axis = AXIS_SCREENSIZE;
-        value->intValue = config.screenSize;
-        return true;
-    }
-
-    // version
-    if (getVersionName(part.string(), &config)) {
-        *axis = AXIS_VERSION;
-        value->intValue = config.version;
-        return true;
-    }
-
-    return false;
-}
-
-AxisValue
-AaptGroupEntry::getConfigValueForAxis(const ResTable_config& config, int axis)
-{
-    AxisValue value;
-    switch (axis) {
-        case AXIS_MCC:
-            value.intValue = config.mcc;
-            break;
-        case AXIS_MNC:
-            value.intValue = config.mnc;
-            break;
-        case AXIS_LOCALE:
-            value.localeValue.initFromResTable(config);
-            break;
-        case AXIS_LAYOUTDIR:
-            value.intValue = config.screenLayout&ResTable_config::MASK_LAYOUTDIR;
-            break;
-        case AXIS_SCREENLAYOUTSIZE:
-            value.intValue = config.screenLayout&ResTable_config::MASK_SCREENSIZE;
-            break;
-        case AXIS_ORIENTATION:
-            value.intValue = config.orientation;
-            break;
-        case AXIS_UIMODETYPE:
-            value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
-            break;
-        case AXIS_UIMODENIGHT:
-            value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
-            break;
-        case AXIS_DENSITY:
-            value.intValue = config.density;
-            break;
-        case AXIS_TOUCHSCREEN:
-            value.intValue = config.touchscreen;
-            break;
-        case AXIS_KEYSHIDDEN:
-            value.intValue = config.inputFlags;
-            break;
-        case AXIS_KEYBOARD:
-            value.intValue = config.keyboard;
-            break;
-        case AXIS_NAVIGATION:
-            value.intValue = config.navigation;
-            break;
-        case AXIS_SCREENSIZE:
-            value.intValue = config.screenSize;
-            break;
-        case AXIS_SMALLESTSCREENWIDTHDP:
-            value.intValue = config.smallestScreenWidthDp;
-            break;
-        case AXIS_SCREENWIDTHDP:
-            value.intValue = config.screenWidthDp;
-            break;
-        case AXIS_SCREENHEIGHTDP:
-            value.intValue = config.screenHeightDp;
-            break;
-        case AXIS_VERSION:
-            value.intValue = config.version;
-            break;
-    }
-
-    return value;
-}
-
-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;
+    const char* q = strchr(dir, '-');
+    size_t typeLen;
+    if (q != NULL) {
+        typeLen = q - dir;
+    } else {
+        typeLen = strlen(dir);
+    }
 
-    Vector<String8> parts;
-    AaptLocaleValue::splitAndLowerCase(dir, &parts, '-');
-
-    String8 mcc, mnc, layoutsize, layoutlong, orient, den;
-    String8 touch, key, keysHidden, nav, navHidden, size, layoutDir, vers;
-    String8 uiModeType, uiModeNight, smallestwidthdp, widthdp, heightdp;
-
-    AaptLocaleValue locale;
-    int numLocaleComponents = 0;
-
-    const int N = parts.size();
-    int index = 0;
-    String8 part = parts[index];
-
-    // resource type
-    if (!isValidResourceType(part)) {
+    String8 type(dir, typeLen);
+    if (!isValidResourceType(type)) {
         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;
+    if (q != NULL) {
+        if (!AaptConfig::parse(String8(q + 1), &mParams)) {
+            return false;
         }
-        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 mnc: %s\n", part.string());
-    }
-
-    index = locale.initFromDirName(parts, index);
-    if (index == -1) {
-        return false;
-    }
-    if (index >= N){
-        goto success;
-    }
-
-    part = parts[index];
-    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 = locale;
-    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 = "";
-
+    *resType = type;
     return true;
 }
 
 String8
-AaptGroupEntry::toString() const
-{
-    String8 s = this->mcc;
-    s += ",";
-    s += this->mnc;
-    s += ",";
-    s += locale.toDirName();
-    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 != "") {
+    String8 params = mParams.toString();
+    if (params.length() > 0) {
         if (s.length() > 0) {
             s += "-";
         }
-        s += mcc;
+        s += params;
     }
-    if (this->mnc != "") {
-        if (s.length() > 0) {
-            s += "-";
-        }
-        s += mnc;
-    }
-
-    const String8 localeComponent = locale.toDirName();
-    if (localeComponent != "") {
-         if (s.length() > 0) {
-             s += "-";
-         }
-         s += localeComponent;
-    }
-
-    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;
-}
-
-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;
-    } else if (strcmp(name, "watch") == 0) {
-      if (out) out->uiMode =
-              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
-              | ResTable_config::UI_MODE_TYPE_WATCH;
-        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(ResTable_config));
-    getMccName(mcc.string(), &params);
-    getMncName(mnc.string(), &params);
-    locale.writeTo(&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;
-}
 
 // =========================================================================
 // =========================================================================
@@ -2229,9 +907,7 @@
     : AaptDir(String8(), String8()),
       mHavePrivateSymbols(false),
       mChanged(false), mHaveIncludedAssets(false),
-      mRes(NULL)
-{
-}
+      mRes(NULL) {}
 
 const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const {
     if (mChanged) {
@@ -2506,7 +1182,7 @@
         String8 resType;
         bool b = group.initFromDirName(entry->d_name, &resType);
         if (!b) {
-            fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(),
+            fprintf(stderr, "invalid resource directory name: %s %s\n", srcDir.string(),
                     entry->d_name);
             err = -1;
             continue;
@@ -2654,30 +1330,35 @@
 
 status_t AaptAssets::filter(Bundle* bundle)
 {
-    ResourceFilter reqFilter;
+    WeakResourceFilter 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;
+    uint32_t preferredDensity = 0;
+    if (bundle->getPreferredDensity().size() > 0) {
+        ResTable_config preferredConfig;
+        if (!AaptConfig::parseDensity(bundle->getPreferredDensity().string(), &preferredConfig)) {
+            fprintf(stderr, "Error parsing preferred density: %s\n",
+                    bundle->getPreferredDensity().string());
+            return UNKNOWN_ERROR;
+        }
+        preferredDensity = preferredConfig.density;
     }
 
-    if (reqFilter.isEmpty() && prefFilter.isEmpty()) {
+    if (reqFilter.isEmpty() && preferredDensity == 0) {
         return NO_ERROR;
     }
 
     if (bundle->getVerbose()) {
         if (!reqFilter.isEmpty()) {
             printf("Applying required filter: %s\n",
-                    bundle->getConfigurations());
+                    bundle->getConfigurations().string());
         }
-        if (!prefFilter.isEmpty()) {
-            printf("Applying preferred filter: %s\n",
-                    bundle->getPreferredConfigurations());
+        if (preferredDensity > 0) {
+            printf("Applying preferred density filter: %s\n",
+                    bundle->getPreferredDensity().string());
         }
     }
 
@@ -2734,89 +1415,71 @@
             }
 
             // Quick check: no preferred filters, nothing more to do.
-            if (prefFilter.isEmpty()) {
+            if (preferredDensity == 0) {
                 continue;
             }
 
             // Get the preferred density if there is one. We do not match exactly for density.
             // If our preferred density is hdpi but we only have mdpi and xhdpi resources, we
             // pick xhdpi.
-            uint32_t preferredDensity = 0;
-            const SortedVector<AxisValue>* preferredConfigs = prefFilter.configsForAxis(AXIS_DENSITY);
-            if (preferredConfigs != NULL && preferredConfigs->size() > 0) {
-                preferredDensity = (*preferredConfigs)[0].intValue;
-            }
+            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 (config.density != 0 && config.density != preferredDensity) {
+                    // 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.
+                    uint32_t bestDensity = config.density;
 
-            // 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.
-
-                        uint32_t bestDensity = config.density;
-
-                        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 (axis == AXIS_DENSITY && preferredDensity > 0) {
-                                    // See if there is a better density resource
-                                    if (mconfig.density < bestDensity &&
-                                            mconfig.density > preferredDensity &&
-                                            bestDensity > preferredDensity) {
-                                        // This density is between our best density and
-                                        // the preferred density, therefore it is better.
-                                        bestDensity = mconfig.density;
-                                    } else if (mconfig.density > bestDensity &&
-                                            bestDensity < preferredDensity) {
-                                        // This density is better than our best density and
-                                        // our best density was smaller than our preferred
-                                        // density, so it is better.
-                                        bestDensity = mconfig.density;
-                                    }
-                                } else if (prefFilter.match(axis, mconfig)) {
-                                    if (bundle->getVerbose()) {
-                                        printf("Pruning unneeded resource: %s\n",
-                                                file->getPrintableSource().string());
-                                    }
-                                    grp->removeFile(k);
-                                    k--;
-                                    break;
-                                }
-                            }
+                    for (size_t m=0; m<grp->getFiles().size(); m++) {
+                        if (m == k) {
+                            continue;
                         }
 
-                        if (axis == AXIS_DENSITY && preferredDensity > 0 &&
-                                bestDensity != config.density) {
-                            if (bundle->getVerbose()) {
-                                printf("Pruning unneeded resource: %s\n",
-                                        file->getPrintableSource().string());
+                        sp<AaptFile> mfile = grp->getFiles().valueAt(m);
+                        const ResTable_config& mconfig(mfile->getGroupEntry().toParams());
+                        if (AaptConfig::isSameExcept(config, mconfig, ResTable_config::CONFIG_DENSITY)) {
+                            // See if there is a better density resource
+                            if (mconfig.density < bestDensity &&
+                                    mconfig.density > preferredDensity &&
+                                    bestDensity > preferredDensity) {
+                                // This density is between our best density and
+                                // the preferred density, therefore it is better.
+                                bestDensity = mconfig.density;
+                            } else if (mconfig.density > bestDensity &&
+                                    bestDensity < preferredDensity) {
+                                // This density is better than our best density and
+                                // our best density was smaller than our preferred
+                                // density, so it is better.
+                                bestDensity = mconfig.density;
                             }
-                            grp->removeFile(k);
-                            k--;
                         }
                     }
+
+                    if (bestDensity != config.density) {
+                        if (bundle->getVerbose()) {
+                            printf("Pruning unneeded resource: %s\n",
+                                    file->getPrintableSource().string());
+                        }
+                        grp->removeFile(k);
+                        k--;
+                    }
                 }
             }
         }
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index 82dda5f..0c2576a 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -6,22 +6,24 @@
 #ifndef __AAPT_ASSETS_H
 #define __AAPT_ASSETS_H
 
-#include <stdlib.h>
 #include <androidfw/AssetManager.h>
 #include <androidfw/ResourceTypes.h>
+#include <stdlib.h>
+#include <set>
 #include <utils/KeyedVector.h>
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
+
+#include "AaptConfig.h"
+#include "Bundle.h"
+#include "ConfigDescription.h"
+#include "SourcePos.h"
 #include "ZipFile.h"
 
-#include "Bundle.h"
-#include "SourcePos.h"
-
 using namespace android;
 
-
 extern const char * const gDefaultIgnoreAssets;
 extern const char * gUserIgnoreAssets;
 
@@ -82,9 +84,6 @@
          return memcmp(this, &other, sizeof(AaptLocaleValue));
      }
 
-     static void splitAndLowerCase(const char* const chars, Vector<String8>* parts,
-             const char separator);
-
      inline bool operator<(const AaptLocaleValue& o) const { return compare(o) < 0; }
      inline bool operator<=(const AaptLocaleValue& o) const { return compare(o) <= 0; }
      inline bool operator==(const AaptLocaleValue& o) const { return compare(o) == 0; }
@@ -98,31 +97,6 @@
      void setVariant(const char* variant);
 };
 
-struct AxisValue {
-    // Used for all axes except AXIS_LOCALE, which is represented
-    // as a AaptLocaleValue value.
-    int intValue;
-    AaptLocaleValue localeValue;
-
-    AxisValue() : intValue(0) {
-    }
-
-    inline int compare(const AxisValue &other) const  {
-        if (intValue != other.intValue) {
-            return intValue - other.intValue;
-        }
-
-        return localeValue.compare(other.localeValue);
-    }
-
-    inline bool operator<(const AxisValue& o) const { return compare(o) < 0; }
-    inline bool operator<=(const AxisValue& o) const { return compare(o) <= 0; }
-    inline bool operator==(const AxisValue& o) const { return compare(o) == 0; }
-    inline bool operator!=(const AxisValue& o) const { return compare(o) != 0; }
-    inline bool operator>=(const AxisValue& o) const { return compare(o) >= 0; }
-    inline bool operator>(const AxisValue& o) const { return compare(o) > 0; }
-};
-
 /**
  * This structure contains a specific variation of a single file out
  * of all the variations it can have that we can have.
@@ -130,23 +104,11 @@
 struct AaptGroupEntry
 {
 public:
-    AaptGroupEntry() : mParamsChanged(true) {
-        memset(&mParams, 0, sizeof(ResTable_config));
-    }
-
     bool initFromDirName(const char* dir, String8* resType);
 
-    static bool parseFilterNamePart(const String8& part, int* axis, AxisValue* value);
+    inline const ConfigDescription& toParams() const { return mParams; }
 
-    static AxisValue getConfigValueForAxis(const ResTable_config& config, int axis);
-
-    static bool configSameExcept(const ResTable_config& config,
-            const ResTable_config& otherConfig, int axis);
-
-    int compare(const AaptGroupEntry& o) const;
-
-    const ResTable_config toParams() const;
-
+    inline int compare(const AaptGroupEntry& o) const { return mParams.compareLogical(o.mParams); }
     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; }
@@ -154,56 +116,13 @@
     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 toString() const { return mParams.toString(); }
     String8 toDirName(const String8& resType) const;
 
-    const String8& getVersionString() const { return version; }
+    const String8 getVersionString() const { return AaptConfig::getVersion(mParams); }
 
 private:
-    static bool getMccName(const char* name, ResTable_config* out = NULL);
-    static bool getMncName(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);
-
-    String8 mcc;
-    String8 mnc;
-    AaptLocaleValue 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;
+    ConfigDescription mParams;
 };
 
 inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
new file mode 100644
index 0000000..69a9c7f
--- /dev/null
+++ b/tools/aapt/AaptConfig.cpp
@@ -0,0 +1,790 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+#include <ctype.h>
+
+#include "AaptConfig.h"
+#include "AaptAssets.h"
+#include "AaptUtil.h"
+#include "ResourceFilter.h"
+
+using android::String8;
+using android::Vector;
+using android::ResTable_config;
+
+namespace AaptConfig {
+
+static const char* kWildcardName = "any";
+
+bool parse(const String8& str, ConfigDescription* out) {
+    Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '-');
+
+    ConfigDescription config;
+    AaptLocaleValue locale;
+    ssize_t index = 0;
+    ssize_t localeIndex = 0;
+    const ssize_t N = parts.size();
+    const char* part = parts[index].string();
+
+    if (str.length() == 0) {
+        goto success;
+    }
+
+    if (parseMcc(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseMnc(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    // Locale spans a few '-' separators, so we let it
+    // control the index.
+    localeIndex = locale.initFromDirName(parts, index);
+    if (localeIndex < 0) {
+        return false;
+    } else if (localeIndex > index) {
+        locale.writeTo(&config);
+        index = localeIndex;
+        if (index >= N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseLayoutDirection(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseSmallestScreenWidthDp(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenWidthDp(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenHeightDp(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenLayoutSize(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenLayoutLong(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseOrientation(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseUiModeType(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseUiModeNight(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseDensity(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseTouchscreen(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseKeysHidden(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseKeyboard(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseNavHidden(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseNavigation(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseScreenSize(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    if (parseVersion(part, &config)) {
+        index++;
+        if (index == N) {
+            goto success;
+        }
+        part = parts[index].string();
+    }
+
+    // Unrecognized.
+    return false;
+
+success:
+    if (out != NULL) {
+        applyVersionForCompatibility(&config);
+        *out = config;
+    }
+    return true;
+}
+
+bool parseCommaSeparatedList(const String8& str, std::set<ConfigDescription>* outSet) {
+    Vector<String8> parts = AaptUtil::splitAndLowerCase(str, ',');
+    const size_t N = parts.size();
+    for (size_t i = 0; i < N; i++) {
+        ConfigDescription config;
+        if (!parse(parts[i], &config)) {
+            return false;
+        }
+        outSet->insert(config);
+    }
+    return true;
+}
+
+void applyVersionForCompatibility(ConfigDescription* config) {
+    if (config == NULL) {
+        return;
+    }
+
+    uint16_t minSdk = 0;
+    if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
+            || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+        minSdk = SDK_HONEYCOMB_MR2;
+    } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
+                != ResTable_config::UI_MODE_TYPE_ANY
+            ||  (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT)
+                != ResTable_config::UI_MODE_NIGHT_ANY) {
+        minSdk = SDK_FROYO;
+    } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE)
+                != ResTable_config::SCREENSIZE_ANY
+            ||  (config->screenLayout & ResTable_config::MASK_SCREENLONG)
+                != ResTable_config::SCREENLONG_ANY
+            || config->density != ResTable_config::DENSITY_DEFAULT) {
+        minSdk = SDK_DONUT;
+    }
+
+    if (minSdk > config->sdkVersion) {
+        config->sdkVersion = minSdk;
+    }
+}
+
+bool parseMcc(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 parseMnc(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;
+}
+
+bool parseLayoutDirection(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 parseScreenLayoutSize(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 parseScreenLayoutLong(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 parseOrientation(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 parseUiModeType(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;
+    } else if (strcmp(name, "watch") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_WATCH;
+        return true;
+    }
+
+    return false;
+}
+
+bool parseUiModeNight(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 parseDensity(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 parseTouchscreen(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 parseKeysHidden(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 parseKeyboard(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 parseNavHidden(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 parseNavigation(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 parseScreenSize(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 parseSmallestScreenWidthDp(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 parseScreenWidthDp(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 parseScreenHeightDp(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 parseVersion(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;
+}
+
+String8 getVersion(const ResTable_config& config) {
+    return String8::format("v%u", config.sdkVersion);
+}
+
+bool isSameExcept(const ResTable_config& a, const ResTable_config& b, int axisMask) {
+    return a.diff(b) == axisMask;
+}
+
+} // namespace AaptConfig
diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h
new file mode 100644
index 0000000..2963539
--- /dev/null
+++ b/tools/aapt/AaptConfig.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 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_CONFIG_H
+#define __AAPT_CONFIG_H
+
+#include <set>
+#include <utils/String8.h>
+
+#include "ConfigDescription.h"
+
+/**
+ * Utility methods for dealing with configurations.
+ */
+namespace AaptConfig {
+
+/**
+ * Parse a string of the form 'fr-sw600dp-land' and fill in the
+ * given ResTable_config with resulting configuration parameters.
+ *
+ * The resulting configuration has the appropriate sdkVersion defined
+ * for backwards compatibility.
+ */
+bool parse(const android::String8& str, ConfigDescription* out = NULL);
+
+/**
+ * Parse a comma separated list of configuration strings. Duplicate configurations
+ * will be removed.
+ *
+ * Example input: "fr,de-land,fr-sw600dp-land"
+ */
+bool parseCommaSeparatedList(const android::String8& str, std::set<ConfigDescription>* outSet);
+
+/**
+ * If the configuration uses an axis that was added after
+ * the original Android release, make sure the SDK version
+ * is set accordingly.
+ */
+void applyVersionForCompatibility(ConfigDescription* config);
+
+// Individual axis
+bool parseMcc(const char* str, android::ResTable_config* out = NULL);
+bool parseMnc(const char* str, android::ResTable_config* out = NULL);
+bool parseLayoutDirection(const char* str, android::ResTable_config* out = NULL);
+bool parseSmallestScreenWidthDp(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenWidthDp(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenHeightDp(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenLayoutSize(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenLayoutLong(const char* str, android::ResTable_config* out = NULL);
+bool parseOrientation(const char* str, android::ResTable_config* out = NULL);
+bool parseUiModeType(const char* str, android::ResTable_config* out = NULL);
+bool parseUiModeNight(const char* str, android::ResTable_config* out = NULL);
+bool parseDensity(const char* str, android::ResTable_config* out = NULL);
+bool parseTouchscreen(const char* str, android::ResTable_config* out = NULL);
+bool parseKeysHidden(const char* str, android::ResTable_config* out = NULL);
+bool parseKeyboard(const char* str, android::ResTable_config* out = NULL);
+bool parseNavHidden(const char* str, android::ResTable_config* out = NULL);
+bool parseNavigation(const char* str, android::ResTable_config* out = NULL);
+bool parseScreenSize(const char* str, android::ResTable_config* out = NULL);
+bool parseVersion(const char* str, android::ResTable_config* out = NULL);
+
+android::String8 getVersion(const android::ResTable_config& config);
+
+/**
+ * Returns true if the two configurations only differ by the specified axis.
+ * The axis mask is a bitmask of CONFIG_* constants.
+ */
+bool isSameExcept(const android::ResTable_config& a, const android::ResTable_config& b, int configMask);
+
+} // namespace AaptConfig
+
+#endif // __AAPT_CONFIG_H
diff --git a/tools/aapt/AaptUtil.cpp b/tools/aapt/AaptUtil.cpp
new file mode 100644
index 0000000..293e144
--- /dev/null
+++ b/tools/aapt/AaptUtil.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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 "AaptUtil.h"
+
+using android::Vector;
+using android::String8;
+
+namespace AaptUtil {
+
+Vector<String8> split(const String8& str, const char sep) {
+    Vector<String8> parts;
+    const char* p = str.string();
+    const char* q;
+
+    while (true) {
+        q = strchr(p, sep);
+        if (q == NULL) {
+            parts.add(String8(p, strlen(p)));
+            return parts;
+        }
+
+        parts.add(String8(p, q-p));
+        p = q + 1;
+    }
+    return parts;
+}
+
+Vector<String8> splitAndLowerCase(const String8& str, const char sep) {
+    Vector<String8> parts;
+    const char* p = str.string();
+    const char* q;
+
+    while (true) {
+        q = strchr(p, sep);
+        if (q == NULL) {
+            String8 val(p, strlen(p));
+            val.toLower();
+            parts.add(val);
+            return parts;
+        }
+
+        String8 val(p, q-p);
+        val.toLower();
+        parts.add(val);
+        p = q + 1;
+    }
+    return parts;
+}
+
+} // namespace AaptUtil
diff --git a/tools/aapt/AaptUtil.h b/tools/aapt/AaptUtil.h
new file mode 100644
index 0000000..47a704a
--- /dev/null
+++ b/tools/aapt/AaptUtil.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 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_UTIL_H
+#define __AAPT_UTIL_H
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace AaptUtil {
+
+android::Vector<android::String8> split(const android::String8& str, const char sep);
+android::Vector<android::String8> splitAndLowerCase(const android::String8& str, const char sep);
+
+} // namespace AaptUtil
+
+#endif // __AAPT_UTIL_H
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index 806f8ff..700afa1 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -1,104 +1,168 @@
-# 
-# Copyright 2006 The Android Open Source Project
 #
-# Android Asset Packaging Tool
+# Copyright (C) 2014 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.
 #
 
 # This tool is prebuilt if we're doing an app-only build.
 ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),)
 
+# ==========================================================
+# Setup some common variables for the different build
+# targets here.
+# ==========================================================
+LOCAL_PATH:= $(call my-dir)
 
-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 \
+aaptMain := Main.cpp
+aaptSources := \
+    AaptAssets.cpp \
+    AaptConfig.cpp \
+    AaptUtil.cpp \
+    ApkBuilder.cpp \
+    Command.cpp \
+    CrunchCache.cpp \
+    FileFinder.cpp \
+    Package.cpp \
+    StringPool.cpp \
+    XMLNode.cpp \
+    ResourceFilter.cpp \
+    ResourceIdCache.cpp \
+    ResourceTable.cpp \
+    Images.cpp \
+    Resource.cpp \
     pseudolocalize.cpp \
     SourcePos.cpp \
-	WorkQueue.cpp \
+    WorkQueue.cpp \
     ZipEntry.cpp \
     ZipFile.cpp \
-	qsort_r_compat.c
+    qsort_r_compat.c
 
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
+aaptTests := \
+    tests/AaptConfig_test.cpp \
+    tests/AaptGroupEntry_test.cpp \
+    tests/ResourceFilter_test.cpp
 
-LOCAL_SRC_FILES := $(aapt_src_files)
+aaptCIncludes := \
+    external/libpng \
+    external/zlib
 
-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 \
-	libziparchive-host
+aaptHostLdLibs :=
+aaptHostStaticLibs := \
+    libandroidfw \
+    libpng \
+    liblog \
+    libutils \
+    libcutils \
+    libexpat \
+    libziparchive-host
 
 ifeq ($(HOST_OS),linux)
-LOCAL_LDLIBS += -lrt -ldl -lpthread
+    aaptHostLdLibs += -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
+    aaptHostStaticLibs += libz
 else
-  LOCAL_LDLIBS += -lz
+    aaptHostLdLibs += -lz
 endif
 
+
+# ==========================================================
+# Build the host static library: libaapt
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaapt
+
+LOCAL_SRC_FILES := $(aaptSources)
+LOCAL_C_INCLUDES += $(aaptCIncludes)
+
+LOCAL_CFLAGS += -Wno-format-y2k
+LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
+ifeq (darwin,$(HOST_OS))
+LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS
+endif
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# ==========================================================
+# Build the host executable: aapt
+# ==========================================================
+include $(CLEAR_VARS)
+
 LOCAL_MODULE := aapt
 
+LOCAL_SRC_FILES := $(aaptMain)
+
+LOCAL_STATIC_LIBRARIES += \
+    libaapt \
+    $(aaptHostStaticLibs)
+LOCAL_LDLIBS += $(aaptHostLdLibs)
+
 include $(BUILD_HOST_EXECUTABLE)
 
-# aapt for running on the device
-# =========================================================
+
+# ==========================================================
+# Build the host tests: libaapt_tests
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaapt_tests
+
+LOCAL_SRC_FILES += $(aaptTests)
+LOCAL_C_INCLUDES += $(LOCAL_PATH)
+
+LOCAL_STATIC_LIBRARIES += \
+    libaapt \
+    $(aaptHostStaticLibs)
+LOCAL_LDLIBS += $(aaptHostLdLibs)
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+
+# ==========================================================
+# Build the device executable: aapt
+# ==========================================================
 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_SRC_FILES := $(aaptSources) $(aaptMain)
+LOCAL_C_INCLUDES += \
+    $(aaptCIncludes) \
+    bionic \
+    external/stlport/stlport
 
 LOCAL_SHARED_LIBRARIES := \
-        libandroidfw \
-        libutils \
-        libcutils \
-        libpng \
-        liblog \
-        libz
+    libandroidfw \
+    libutils \
+    libcutils \
+    libpng \
+    liblog \
+    libz
 
 LOCAL_STATIC_LIBRARIES := \
-        libstlport_static \
-        libexpat_static
+    libstlport_static \
+    libexpat_static
+
+LOCAL_CPPFLAGS += -Wno-non-virtual-dtor
 
 include $(BUILD_EXECUTABLE)
-endif
+
+endif # Not SDK_ONLY
 
 endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
diff --git a/tools/aapt/ApkBuilder.cpp b/tools/aapt/ApkBuilder.cpp
new file mode 100644
index 0000000..12f6040
--- /dev/null
+++ b/tools/aapt/ApkBuilder.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 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 "AaptAssets.h"
+#include "ApkBuilder.h"
+
+using namespace android;
+
+ApkBuilder::ApkBuilder(const sp<WeakResourceFilter>& configFilter)
+    : mConfigFilter(configFilter)
+    , mDefaultFilter(new AndResourceFilter()) {
+    // Add the default split, which is present for all APKs.
+    mDefaultFilter->addFilter(mConfigFilter);
+    mSplits.add(new ApkSplit(std::set<ConfigDescription>(), mDefaultFilter, true));
+}
+
+status_t ApkBuilder::createSplitForConfigs(const std::set<ConfigDescription>& configs) {
+    const size_t N = mSplits.size();
+    for (size_t i = 0; i < N; i++) {
+        const std::set<ConfigDescription>& splitConfigs = mSplits[i]->getConfigs();
+        std::set<ConfigDescription>::const_iterator iter = configs.begin();
+        for (; iter != configs.end(); iter++) {
+            if (splitConfigs.count(*iter) > 0) {
+                // Can't have overlapping configurations.
+                fprintf(stderr, "ERROR: Split configuration '%s' is already defined "
+                        "in another split.\n", iter->toString().string());
+                return ALREADY_EXISTS;
+            }
+        }
+    }
+
+    sp<StrongResourceFilter> splitFilter = new StrongResourceFilter(configs);
+
+    // Add the inverse filter of this split filter to the base apk filter so it will
+    // omit resources that belong in this split.
+    mDefaultFilter->addFilter(new InverseResourceFilter(splitFilter));
+
+    // Now add the apk-wide config filter to our split filter.
+    sp<AndResourceFilter> filter = new AndResourceFilter();
+    filter->addFilter(splitFilter);
+    filter->addFilter(mConfigFilter);
+    mSplits.add(new ApkSplit(configs, filter));
+    return NO_ERROR;
+}
+
+status_t ApkBuilder::addEntry(const String8& path, const sp<AaptFile>& file) {
+    const size_t N = mSplits.size();
+    for (size_t i = 0; i < N; i++) {
+        if (mSplits[i]->matches(file)) {
+            return mSplits.editItemAt(i)->addEntry(path, file);
+        }
+    }
+    // Entry can be dropped if it doesn't match any split. This will only happen
+    // if the enry doesn't mConfigFilter.
+    return NO_ERROR;
+}
+
+void ApkBuilder::print() const {
+    fprintf(stderr, "APK Builder\n");
+    fprintf(stderr, "-----------\n");
+    const size_t N = mSplits.size();
+    for (size_t i = 0; i < N; i++) {
+        mSplits[i]->print();
+        fprintf(stderr, "\n");
+    }
+}
+
+ApkSplit::ApkSplit(const std::set<ConfigDescription>& configs, const sp<ResourceFilter>& filter, bool isBase)
+    : mConfigs(configs), mFilter(filter), mIsBase(isBase) {
+    std::set<ConfigDescription>::const_iterator iter = configs.begin();
+    for (; iter != configs.end(); iter++) {
+        if (mName.size() > 0) {
+            mName.append(",");
+            mDirName.append("_");
+        }
+
+        String8 configStr = iter->toString();
+        mName.append(configStr);
+        mDirName.append(configStr);
+    }
+}
+
+status_t ApkSplit::addEntry(const String8& path, const sp<AaptFile>& file) {
+    if (!mFiles.insert(OutputEntry(path, file)).second) {
+        // Duplicate file.
+        return ALREADY_EXISTS;
+    }
+    return NO_ERROR;
+}
+
+void ApkSplit::print() const {
+    fprintf(stderr, "APK Split '%s'\n", mName.string());
+
+    std::set<OutputEntry>::const_iterator iter = mFiles.begin();
+    for (; iter != mFiles.end(); iter++) {
+        fprintf(stderr, "  %s (%s)\n", iter->getPath().string(), iter->getFile()->getSourceFile().string());
+    }
+}
diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h
new file mode 100644
index 0000000..a4b7d4a
--- /dev/null
+++ b/tools/aapt/ApkBuilder.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2014 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 __APK_BUILDER_H
+#define __APK_BUILDER_H
+
+#include <set>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+#include "ConfigDescription.h"
+#include "OutputSet.h"
+#include "ResourceFilter.h"
+
+class ApkSplit;
+class AaptFile;
+
+class ApkBuilder : public android::RefBase {
+public:
+    ApkBuilder(const sp<WeakResourceFilter>& configFilter);
+
+    /**
+     * Tells the builder to generate a separate APK for resources that
+     * match the configurations specified. Split APKs can not have
+     * overlapping resources.
+     *
+     * NOTE: All splits should be set up before any files are added.
+     */
+    android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs);
+
+    /**
+     * Adds a file to be written to the final APK. It's name must not collide
+     * with that of any files previously added. When a Split APK is being
+     * generated, duplicates can exist as long as they are in different splits
+     * (resources.arsc, AndroidManifest.xml).
+     */
+    android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file);
+
+    android::Vector<sp<ApkSplit> >& getSplits() {
+        return mSplits;
+    }
+
+    void print() const;
+
+private:
+    android::sp<ResourceFilter> mConfigFilter;
+    android::sp<AndResourceFilter> mDefaultFilter;
+    android::Vector<sp<ApkSplit> > mSplits;
+};
+
+class ApkSplit : public OutputSet {
+public:
+    android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file);
+
+    const std::set<OutputEntry>& getEntries() const {
+        return mFiles;
+    }
+
+    const std::set<ConfigDescription>& getConfigs() const {
+        return mConfigs;
+    }
+
+    bool matches(const sp<AaptFile>& file) const {
+        return mFilter->match(file->getGroupEntry().toParams());
+    }
+
+    sp<ResourceFilter> getResourceFilter() const {
+        return mFilter;
+    }
+
+    const android::String8& getPrintableName() const {
+        return mName;
+    }
+
+    const android::String8& getDirectorySafeName() const {
+        return mDirName;
+    }
+
+    bool isBase() const {
+        return mIsBase;
+    }
+
+    void print() const;
+
+private:
+    friend class ApkBuilder;
+
+    ApkSplit(const std::set<ConfigDescription>& configs, const android::sp<ResourceFilter>& filter, bool isBase=false);
+
+    std::set<ConfigDescription> mConfigs;
+    const sp<ResourceFilter> mFilter;
+    const bool mIsBase;
+    String8 mName;
+    String8 mDirName;
+    std::set<OutputEntry> mFiles;
+};
+
+#endif // __APK_BUILDER_H
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index ebe1bed..ceb52a0 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -151,10 +151,12 @@
     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; }
+    const android::String8& getConfigurations() const { return mConfigurations; }
     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 android::String8& getPreferredDensity() const { return mPreferredDensity; }
+    void setPreferredDensity(const char* val) { mPreferredDensity = val; }
+    void addSplitConfigurations(const char* val) { mPartialConfigurations.add(android::String8(val)); }
+    const android::Vector<android::String8>& getSplitConfigurations() const { return mPartialConfigurations; }
     const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; }
     void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; }
     const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; }
@@ -286,7 +288,8 @@
     const char* mRClassDir;
     const char* mResourceIntermediatesDir;
     android::String8 mConfigurations;
-    android::String8 mPreferredConfigurations;
+    android::String8 mPreferredDensity;
+    android::Vector<android::String8> mPartialConfigurations;
     android::Vector<const char*> mPackageIncludes;
     android::Vector<const char*> mJarFiles;
     android::Vector<const char*> mNoCompressExtensions;
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 0af1ce1..0360200 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -3,6 +3,7 @@
 //
 // Android Asset Packaging Tool main entry point.
 //
+#include "ApkBuilder.h"
 #include "Main.h"
 #include "Bundle.h"
 #include "ResourceFilter.h"
@@ -2034,6 +2035,47 @@
     return (result != NO_ERROR);
 }
 
+static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder) {
+    const size_t numDirs = dir->getDirs().size();
+    for (size_t i = 0; i < numDirs; i++) {
+        status_t err = addResourcesToBuilder(dir->getDirs().valueAt(i), builder);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    const size_t numFiles = dir->getFiles().size();
+    for (size_t i = 0; i < numFiles; i++) {
+        sp<AaptGroup> gp = dir->getFiles().valueAt(i);
+        const size_t numConfigs = gp->getFiles().size();
+        for (size_t j = 0; j < numConfigs; j++) {
+            status_t err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j));
+            if (err != NO_ERROR) {
+                fprintf(stderr, "Failed to add %s (%s) to builder.\n",
+                        gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string());
+                return err;
+            }
+        }
+    }
+    return NO_ERROR;
+}
+
+static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) {
+    if (split->isBase()) {
+        return original;
+    }
+
+    String8 ext(original.getPathExtension());
+    if (ext == String8(".apk")) {
+        return String8::format("%s_%s%s",
+                original.getBasePath().string(),
+                split->getDirectorySafeName().string(),
+                ext.string());
+    }
+
+    return String8::format("%s_%s", original.string(),
+            split->getDirectorySafeName().string());
+}
 
 /*
  * Package up an asset directory and associated application files.
@@ -2047,17 +2089,18 @@
     int N;
     FILE* fp;
     String8 dependencyFile;
+    sp<ApkBuilder> builder;
 
     // -c en_XA or/and ar_XB means do pseudolocalization
-    ResourceFilter filter;
-    err = filter.parse(bundle->getConfigurations());
+    sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
+    err = configFilter->parse(bundle->getConfigurations());
     if (err != NO_ERROR) {
         goto bail;
     }
-    if (filter.containsPseudo()) {
+    if (configFilter->containsPseudo()) {
         bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
     }
-    if (filter.containsPseudoBidi()) {
+    if (configFilter->containsPseudoBidi()) {
         bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
     }
 
@@ -2105,9 +2148,32 @@
         assets->print(String8());
     }
 
+    // Create the ApkBuilder, which will collect the compiled files
+    // to write to the final APK (or sets of APKs if we are building
+    // a Split APK.
+    builder = new ApkBuilder(configFilter);
+
+    // If we are generating a Split APK, find out which configurations to split on.
+    if (bundle->getSplitConfigurations().size() > 0) {
+        const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
+        const size_t numSplits = splitStrs.size();
+        for (size_t i = 0; i < numSplits; i++) {
+            std::set<ConfigDescription> configs;
+            if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
+                fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
+                goto bail;
+            }
+
+            err = builder->createSplitForConfigs(configs);
+            if (err != NO_ERROR) {
+                goto bail;
+            }
+        }
+    }
+
     // If they asked for any fileAs that need to be compiled, do so.
     if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
-        err = buildResources(bundle, assets);
+        err = buildResources(bundle, assets, builder);
         if (err != 0) {
             goto bail;
         }
@@ -2194,11 +2260,24 @@
 
     // Write the apk
     if (outputAPKFile) {
-        err = writeAPK(bundle, assets, String8(outputAPKFile));
+        // Gather all resources and add them to the APK Builder. The builder will then
+        // figure out which Split they belong in.
+        err = addResourcesToBuilder(assets, builder);
         if (err != NO_ERROR) {
-            fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
             goto bail;
         }
+
+        const Vector<sp<ApkSplit> >& splits = builder->getSplits();
+        const size_t numSplits = splits.size();
+        for (size_t i = 0; i < numSplits; i++) {
+            const sp<ApkSplit>& split = splits[i];
+            String8 outputPath = buildApkName(String8(outputAPKFile), split);
+            err = writeAPK(bundle, outputPath, split);
+            if (err != NO_ERROR) {
+                fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
+                goto bail;
+            }
+        }
     }
 
     // If we've been asked to generate a dependency file, we need to finish up here.
diff --git a/tools/aapt/ConfigDescription.h b/tools/aapt/ConfigDescription.h
new file mode 100644
index 0000000..779c423
--- /dev/null
+++ b/tools/aapt/ConfigDescription.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 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 __CONFIG_DESCRIPTION_H
+#define __CONFIG_DESCRIPTION_H
+
+#include <androidfw/ResourceTypes.h>
+
+/**
+ * Subclass of ResTable_config that adds convenient
+ * initialization and comparison methods.
+ */
+struct ConfigDescription : public android::ResTable_config {
+    ConfigDescription() {
+        memset(this, 0, sizeof(*this));
+        size = sizeof(android::ResTable_config);
+    }
+    ConfigDescription(const android::ResTable_config&o) {
+        *static_cast<android::ResTable_config*>(this) = o;
+        size = sizeof(android::ResTable_config);
+    }
+    ConfigDescription(const ConfigDescription&o) {
+        *static_cast<android::ResTable_config*>(this) = o;
+    }
+
+    ConfigDescription& operator=(const android::ResTable_config& o) {
+        *static_cast<android::ResTable_config*>(this) = o;
+        size = sizeof(android::ResTable_config);
+        return *this;
+    }
+    ConfigDescription& operator=(const ConfigDescription& o) {
+        *static_cast<android::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; }
+};
+
+#endif // __CONFIG_DESCRIPTION_H
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 1cf4783..5a60014 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -70,6 +70,7 @@
         "        [-F apk-file] [-J R-file-dir] \\\n"
         "        [--product product1,product2,...] \\\n"
         "        [-c CONFIGS] [--preferred-configurations CONFIGS] \\\n"
+        "        [--split CONFIGS [--split CONFIGS]] \\\n"
         "        [raw-files-dir [raw-files-dir] ...] \\\n"
         "        [--output-text-symbols DIR]\n"
         "\n"
@@ -166,10 +167,12 @@
         "       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"
+        "   --preferred-density\n"
+        "       Specifies a preference for a particular density. Resources that do not\n"
+        "       match this density and have variants that are a closer match are removed.\n"
+        "   --split\n"
+        "       Builds a separate split APK for the configurations listed. This can\n"
+        "       be loaded alongside the base APK at runtime.\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"
@@ -568,15 +571,24 @@
                     bundle.setGenDependencies(true);
                 } else if (strcmp(cp, "-utf16") == 0) {
                     bundle.setWantUTF16(true);
-                } else if (strcmp(cp, "-preferred-configurations") == 0) {
+                } else if (strcmp(cp, "-preferred-density") == 0) {
                     argc--;
                     argv++;
                     if (!argc) {
-                        fprintf(stderr, "ERROR: No argument supplied for '--preferred-configurations' option\n");
+                        fprintf(stderr, "ERROR: No argument supplied for '--preferred-density' option\n");
                         wantUsage = true;
                         goto bail;
                     }
-                    bundle.addPreferredConfigurations(argv[0]);
+                    bundle.setPreferredDensity(argv[0]);
+                } else if (strcmp(cp, "-split") == 0) {
+                    argc--;
+                    argv++;
+                    if (!argc) {
+                        fprintf(stderr, "ERROR: No argument supplied for '--split' option\n");
+                        wantUsage = true;
+                        goto bail;
+                    }
+                    bundle.addSplitConfigurations(argv[0]);
                 } else if (strcmp(cp, "-rename-manifest-package") == 0) {
                     argc--;
                     argv++;
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
index a6b39ac..34c4496 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -10,8 +10,12 @@
 #include <utils/threads.h>
 #include <utils/List.h>
 #include <utils/Errors.h>
-#include "Bundle.h"
+#include <utils/StrongPointer.h>
+
 #include "AaptAssets.h"
+#include "ApkBuilder.h"
+#include "Bundle.h"
+#include "ResourceFilter.h"
 #include "ZipFile.h"
 
 
@@ -22,6 +26,8 @@
     #include <time.h>
 #endif /* BENCHMARK */
 
+class OutputSet;
+
 extern int doVersion(Bundle* bundle);
 extern int doList(Bundle* bundle);
 extern int doDump(Bundle* bundle);
@@ -34,13 +40,13 @@
 extern int calcPercent(long uncompressedLen, long compressedLen);
 
 extern android::status_t writeAPK(Bundle* bundle,
-    const sp<AaptAssets>& assets,
-    const android::String8& outputFile);
+    const android::String8& outputFile,
+    const android::sp<OutputSet>& outputSet);
 
 extern android::status_t updatePreProcessedCache(Bundle* bundle);
 
 extern android::status_t buildResources(Bundle* bundle,
-    const sp<AaptAssets>& assets);
+    const sp<AaptAssets>& assets, sp<ApkBuilder>& builder);
 
 extern android::status_t writeResourceSymbols(Bundle* bundle,
     const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate);
@@ -49,8 +55,6 @@
 
 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);
diff --git a/tools/aapt/OutputSet.h b/tools/aapt/OutputSet.h
new file mode 100644
index 0000000..ea9ef70
--- /dev/null
+++ b/tools/aapt/OutputSet.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 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 __OUTPUT_SET_H
+#define __OUTPUT_SET_H
+
+#include <set>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+
+class AaptFile;
+
+class OutputEntry {
+public:
+    OutputEntry() {}
+    OutputEntry(const android::String8& path, const android::sp<const AaptFile>& file)
+        : mPath(path), mFile(file) {}
+
+    inline const android::sp<const AaptFile>& getFile() const {
+        return mFile;
+    }
+
+    inline const android::String8& getPath() const {
+        return mPath;
+    }
+
+    bool operator<(const OutputEntry& o) const { return getPath() < o.mPath; }
+    bool operator==(const OutputEntry& o) const { return getPath() == o.mPath; }
+
+private:
+    android::String8 mPath;
+    android::sp<const AaptFile> mFile;
+};
+
+class OutputSet : public virtual android::RefBase {
+public:
+    virtual const std::set<OutputEntry>& getEntries() const = 0;
+
+    virtual ~OutputSet() {}
+};
+
+#endif // __OUTPUT_SET_H
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 872d95c..dc16e35 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -5,6 +5,7 @@
 //
 #include "Main.h"
 #include "AaptAssets.h"
+#include "OutputSet.h"
 #include "ResourceTable.h"
 #include "ResourceFilter.h"
 
@@ -36,11 +37,8 @@
 };
 
 /* 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);
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet);
+bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file);
 bool okayToCompress(Bundle* bundle, const String8& pathName);
 ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
 
@@ -51,8 +49,7 @@
  * On success, "bundle->numPackages" will be the number of Zip packages
  * we created.
  */
-status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets,
-                       const String8& outputFile)
+status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet)
 {
     #if BENCHMARK
     fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
@@ -112,7 +109,7 @@
         printf("Writing all files...\n");
     }
 
-    count = processAssets(bundle, zip, assets);
+    count = processAssets(bundle, zip, outputSet);
     if (count < 0) {
         fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
                 outputFile.string());
@@ -218,72 +215,24 @@
     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 processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet)
 {
     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)) {
+    const std::set<OutputEntry>& entries = outputSet->getEntries();
+    std::set<OutputEntry>::const_iterator iter = entries.begin();
+    for (; iter != entries.end(); iter++) {
+        const OutputEntry& entry = *iter;
+        if (entry.getFile() == NULL) {
+            fprintf(stderr, "warning: null file being processed.\n");
+        } else {
+            String8 storagePath(entry.getPath());
+            storagePath.convertToResPath();
+            if (!processFile(bundle, zip, storagePath, entry.getFile())) {
                 return UNKNOWN_ERROR;
             }
             count++;
         }
     }
-
     return count;
 }
 
@@ -294,12 +243,10 @@
  * delete the existing entry before adding the new one.
  */
 bool processFile(Bundle* bundle, ZipFile* zip,
-                 const sp<AaptGroup>& group, const sp<AaptFile>& file)
+                 String8 storageName, const sp<const AaptFile>& file)
 {
     const bool hasData = file->hasData();
 
-    String8 storageName(group->getPath());
-    storageName.convertToResPath();
     ZipEntry* entry;
     bool fromGzip = false;
     status_t result;
@@ -403,8 +350,8 @@
             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());
+            fprintf(stderr, "      Unable to add '%s': Zip add failed (%d)\n",
+                    file->getPrintableSource().string(), result);
         }
         return false;
     }
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index d0581f6..e599643 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -179,24 +179,6 @@
         || 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)
 {
@@ -359,23 +341,6 @@
     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)
 {
@@ -906,7 +871,38 @@
     return 0;
 }
 
-status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
+status_t generateAndroidManifestForSplit(const String16& package, const sp<ApkSplit>& split,
+        sp<AaptFile>& outFile) {
+    const String8 filename("AndroidManifest.xml");
+    const String16 androidPrefix("android");
+    const String16 androidNSUri("http://schemas.android.com/apk/res/android");
+    sp<XMLNode> root = XMLNode::newNamespace(filename, androidPrefix, androidNSUri);
+
+    // Build the <manifest> tag
+    sp<XMLNode> manifest = XMLNode::newElement(filename, String16(), String16("manifest"));
+
+    // Add the 'package' attribute which is set to the original package name.
+    manifest->addAttribute(String16(), String16("package"), package);
+
+    // Add the 'split' attribute which describes the configurations included.
+    String8 splitName("config_");
+    splitName.append(split->getDirectorySafeName());
+    manifest->addAttribute(String16(), String16("split"), String16(splitName));
+
+    // Build an empty <application> tag (required).
+    sp<XMLNode> app = XMLNode::newElement(filename, String16(), String16("application"));
+    manifest->addChild(app);
+    root->addChild(manifest);
+
+    status_t err = root->flatten(outFile, true, true);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    outFile->setCompressionMethod(ZipEntry::kCompressDeflated);
+    return NO_ERROR;
+}
+
+status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
 {
     // First, look for a package file to parse.  This is required to
     // be able to generate the resource information.
@@ -1122,12 +1118,6 @@
     // --------------------------------------------------------------------
 
     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;
@@ -1235,16 +1225,24 @@
     }
 
     if (drawables != NULL) {
-        err = postProcessImages(assets, &table, drawables);
-        if (err != NO_ERROR) {
+        ResourceDirIterator it(drawables, String8("drawable"));
+        while ((err=it.next()) == NO_ERROR) {
+            err = postProcessImage(assets, &table, it.getFile());
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        }
+
+        if (err < NO_ERROR) {
             hasErrors = true;
         }
+        err = NO_ERROR;
     }
 
     if (colors != NULL) {
         ResourceDirIterator it(colors, String8("color"));
         while ((err=it.next()) == NO_ERROR) {
-          err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1261,12 +1259,13 @@
         while ((err=it.next()) == NO_ERROR) {
             String8 src = it.getFile()->getPrintableSource();
             err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
-            if (err != NO_ERROR) {
+            if (err == NO_ERROR) {
+                ResXMLTree block;
+                block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
+                checkForIds(src, block);
+            } else {
                 hasErrors = true;
             }
-            ResXMLTree block;
-            block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
-            checkForIds(src, block);
         }
 
         if (err < NO_ERROR) {
@@ -1319,15 +1318,35 @@
             return err;
         }
 
-        resFile = getResourceFile(assets);
-        if (resFile == NULL) {
-            fprintf(stderr, "Error: unable to generate entry for resource data\n");
-            return UNKNOWN_ERROR;
-        }
+        Vector<sp<ApkSplit> >& splits = builder->getSplits();
+        const size_t numSplits = splits.size();
+        for (size_t i = 0; i < numSplits; i++) {
+            sp<ApkSplit>& split = splits.editItemAt(i);
+            sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"),
+                    AaptGroupEntry(), String8());
+            err = table.flatten(bundle, split->getResourceFilter(), flattenedTable);
+            if (err != NO_ERROR) {
+                fprintf(stderr, "Failed to generate resource table for split '%s'\n",
+                        split->getPrintableName().string());
+                return err;
+            }
+            split->addEntry(String8("resources.arsc"), flattenedTable);
 
-        err = table.flatten(bundle, resFile);
-        if (err < NO_ERROR) {
-            return err;
+            if (split->isBase()) {
+                resFile = flattenedTable;
+                finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
+            } else {
+                sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"),
+                        AaptGroupEntry(), String8());
+                err = generateAndroidManifestForSplit(String16(assets->getPackage()), split,
+                        generatedManifest);
+                if (err != NO_ERROR) {
+                    fprintf(stderr, "Failed to generate AndroidManifest.xml for split '%s'\n",
+                            split->getPrintableName().string());
+                    return err;
+                }
+                split->addEntry(String8("AndroidManifest.xml"), generatedManifest);
+            }
         }
 
         if (bundle->getPublicOutputFile()) {
@@ -1343,18 +1362,13 @@
             table.writePublicDefinitions(String16(assets->getPackage()), fp);
             fclose(fp);
         }
-        
-        // Read resources back in,
-        finalResTable.add(resFile->getData(), resFile->getSize());
-        
-#if 0
-        NOISY(
-              printf("Generated resources:\n");
-              finalResTable.print();
-        )
-#endif
+
+        if (finalResTable.getTableCount() == 0 || resFile == NULL) {
+            fprintf(stderr, "No resource table was generated.\n");
+            return UNKNOWN_ERROR;
+        }
     }
-    
+
     // 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
diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp
index 8ca852e..de8b4fc 100644
--- a/tools/aapt/ResourceFilter.cpp
+++ b/tools/aapt/ResourceFilter.cpp
@@ -1,119 +1,92 @@
 //
-// Copyright 2011 The Android Open Source Project
+// Copyright 2014 The Android Open Source Project
 //
 // Build resource files from raw assets.
 //
 
 #include "ResourceFilter.h"
+#include "AaptUtil.h"
+#include "AaptConfig.h"
 
 status_t
-ResourceFilter::parse(const char* arg)
+WeakResourceFilter::parse(const String8& str)
 {
-    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);
-
+    Vector<String8> configStrs = AaptUtil::split(str, ',');
+    const size_t N = configStrs.size();
+    mConfigs.clear();
+    mConfigMask = 0;
+    mConfigs.resize(N);
+    for (size_t i = 0; i < N; i++) {
+        const String8& part = configStrs[i];
         if (part == "en_XA") {
             mContainsPseudoAccented = true;
         } else if (part == "ar_XB") {
             mContainsPseudoBidi = true;
         }
-        int axis;
-        AxisValue value;
-        if (!AaptGroupEntry::parseFilterNamePart(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;
+
+        std::pair<ConfigDescription, uint32_t>& entry = mConfigs.editItemAt(i);
+
+        AaptLocaleValue val;
+        if (val.initFromFilterString(part)) {
+            // For backwards compatibility, we accept configurations that
+            // only specify locale in the standard 'en_US' format.
+            val.writeTo(&entry.first);
+        } else if (!AaptConfig::parse(part, &entry.first)) {
+            fprintf(stderr, "Invalid configuration: %s\n", part.string());
+            return UNKNOWN_ERROR;
         }
 
-        ssize_t index = mData.indexOfKey(axis);
-        if (index < 0) {
-            mData.add(axis, SortedVector<AxisValue>());
-        }
-        SortedVector<AxisValue>& sv = mData.editValueFor(axis);
-        sv.add(value);
+        entry.second = mDefault.diff(entry.first);
 
-        // If it's a locale with a region, script or variant, we should also match an
-        // unmodified locale of the same language
-        if (axis == AXIS_LOCALE) {
-            if (value.localeValue.region[0] || value.localeValue.script[0] ||
-                value.localeValue.variant[0]) {
-                AxisValue copy;
-                memcpy(copy.localeValue.language, value.localeValue.language,
-                       sizeof(value.localeValue.language));
-                sv.add(copy);
-            }
-        }
-        p = q;
-        if (!*p) break;
-        p++;
+        // Ignore the version
+        entry.second &= ~ResTable_config::CONFIG_VERSION;
+
+        mConfigMask |= entry.second;
     }
 
     return NO_ERROR;
 }
 
 bool
-ResourceFilter::isEmpty() const
+WeakResourceFilter::match(const ResTable_config& config) const
 {
-    return mData.size() == 0;
-}
-
-bool
-ResourceFilter::match(int axis, const AxisValue& value) const
-{
-    if (value.intValue == 0 && (value.localeValue.language[0] == 0)) {
-        // they didn't specify anything so take everything
+    uint32_t mask = mDefault.diff(config);
+    if ((mConfigMask & mask) == 0) {
+        // The two configurations don't have any common axis.
         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<AxisValue>& 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;
+    const size_t N = mConfigs.size();
+    for (size_t i = 0; i < N; i++) {
+        const std::pair<ConfigDescription, uint32_t>& entry = mConfigs[i];
+        uint32_t diff = entry.first.diff(config);
+        if ((diff & entry.second) == 0) {
+            return true;
+        } else if ((diff & entry.second) == ResTable_config::CONFIG_LOCALE) {
+            // If the locales differ, but the languages are the same and
+            // the locale we are matching only has a language specified,
+            // we match.
+            if (config.language[0] && memcmp(config.language, entry.first.language, sizeof(config.language)) == 0) {
+                if (config.country[0] == 0) {
+                    return true;
+                }
+            }
         }
     }
-    return true;
+    return false;
 }
 
-const SortedVector<AxisValue>* ResourceFilter::configsForAxis(int axis) const
-{
-    ssize_t index = mData.indexOfKey(axis);
-    if (index < 0) {
-        return NULL;
+status_t
+StrongResourceFilter::parse(const String8& str) {
+    Vector<String8> configStrs = AaptUtil::split(str, ',');
+    ConfigDescription config;
+    mConfigs.clear();
+    for (size_t i = 0; i < configStrs.size(); i++) {
+        if (!AaptConfig::parse(configStrs[i], &config)) {
+            fprintf(stderr, "Invalid configuration: %s\n", configStrs[i].string());
+            return UNKNOWN_ERROR;
+        }
+        mConfigs.insert(config);
     }
-    return &mData.valueAt(index);
+    return NO_ERROR;
 }
diff --git a/tools/aapt/ResourceFilter.h b/tools/aapt/ResourceFilter.h
index c57770e..f459584 100644
--- a/tools/aapt/ResourceFilter.h
+++ b/tools/aapt/ResourceFilter.h
@@ -7,31 +7,137 @@
 #ifndef RESOURCE_FILTER_H
 #define RESOURCE_FILTER_H
 
+#include <androidfw/ResourceTypes.h>
+#include <set>
+#include <utility>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
 #include "AaptAssets.h"
+#include "ConfigDescription.h"
+
+class ResourceFilter : public virtual android::RefBase {
+public:
+    virtual bool match(const android::ResTable_config& config) const = 0;
+};
 
 /**
  * Implements logic for parsing and handling "-c" and "--preferred-configurations"
  * options.
  */
-class ResourceFilter
-{
+class WeakResourceFilter : public ResourceFilter {
 public:
-    ResourceFilter() : mData(), mContainsPseudoAccented(false),
-        mContainsPseudoBidi(false) {}
-    status_t parse(const char* arg);
-    bool isEmpty() const;
-    bool match(int axis, const ResTable_config& config) const;
-    bool match(const ResTable_config& config) const;
-    const SortedVector<AxisValue>* configsForAxis(int axis) const;
-    inline bool containsPseudo() const { return mContainsPseudoAccented; }
-    inline bool containsPseudoBidi() const { return mContainsPseudoBidi; }
+    WeakResourceFilter()
+        : mContainsPseudoAccented(false)
+        , mContainsPseudoBidi(false) {}
+
+    android::status_t parse(const android::String8& str);
+
+    bool match(const android::ResTable_config& config) const;
+
+    inline bool isEmpty() const {
+        return mConfigMask == 0;
+    }
+
+    inline bool containsPseudo() const {
+        return mContainsPseudoAccented;
+    }
+
+    inline bool containsPseudoBidi() const {
+        return mContainsPseudoBidi;
+    }
 
 private:
-    bool match(int axis, const AxisValue& value) const;
+    ConfigDescription mDefault;
+    uint32_t mConfigMask;
+    android::Vector<std::pair<ConfigDescription, uint32_t> > mConfigs;
 
-    KeyedVector<int,SortedVector<AxisValue> > mData;
     bool mContainsPseudoAccented;
     bool mContainsPseudoBidi;
 };
 
+/**
+ * Matches resources that have at least one of the configurations
+ * that this filter is looking for. In order to match a configuration,
+ * the resource must have the exact same configuration.
+ *
+ * This filter acts as a logical OR when matching resources.
+ *
+ * For example, if the filter is looking for resources with
+ * fr-land, de-land, or sw600dp:
+ *
+ * (PASS) fr-land
+ * (FAIL) fr
+ * (PASS) de-land
+ * (FAIL) de
+ * (FAIL) de-sw600dp
+ * (PASS) sw600dp
+ * (FAIL) sw600dp-land
+ */
+class StrongResourceFilter : public ResourceFilter {
+public:
+    StrongResourceFilter() {}
+    StrongResourceFilter(const std::set<ConfigDescription>& configs)
+        : mConfigs(configs) {}
+
+    android::status_t parse(const android::String8& str);
+
+    bool match(const android::ResTable_config& config) const {
+        std::set<ConfigDescription>::const_iterator iter = mConfigs.begin();
+        for (; iter != mConfigs.end(); iter++) {
+            if (iter->compare(config) == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    inline const std::set<ConfigDescription>& getConfigs() const {
+        return mConfigs;
+    }
+
+private:
+    std::set<ConfigDescription> mConfigs;
+};
+
+/**
+ * Negates the response of the target filter.
+ */
+class InverseResourceFilter : public ResourceFilter {
+public:
+    InverseResourceFilter(const android::sp<ResourceFilter>& filter)
+        : mFilter(filter) {}
+
+    bool match(const android::ResTable_config& config) const {
+        return !mFilter->match(config);
+    }
+
+private:
+    const android::sp<ResourceFilter> mFilter;
+};
+
+/**
+ * A logical AND of all the added filters.
+ */
+class AndResourceFilter : public ResourceFilter {
+public:
+    void addFilter(const android::sp<ResourceFilter>& filter) {
+        mFilters.add(filter);
+    }
+
+    bool match(const android::ResTable_config& config) const {
+        const size_t N = mFilters.size();
+        for (size_t i = 0; i < N; i++) {
+            if (!mFilters[i]->match(config)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+private:
+    android::Vector<android::sp<ResourceFilter> > mFilters;
+};
 #endif
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index b445b8a..337575d 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2080,10 +2080,10 @@
     return mNumLocal > 0;
 }
 
-sp<AaptFile> ResourceTable::flatten(Bundle* bundle)
+sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter)
 {
     sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
-    status_t err = flatten(bundle, data);
+    status_t err = flatten(bundle, filter, data);
     return err == NO_ERROR ? data : NULL;
 }
 
@@ -2643,8 +2643,8 @@
         }
 
         // Check that all requested localizations are present for this string
-        if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) {
-            const char* allConfigs = mBundle->getConfigurations();
+        if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) {
+            const char* allConfigs = mBundle->getConfigurations().string();
             const char* start = allConfigs;
             const char* comma;
             
@@ -2698,14 +2698,8 @@
     return err;
 }
 
-status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest)
+status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, 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();
@@ -2780,7 +2774,7 @@
                 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)) {
+                    if (filterable && !filter->match(config)) {
                         continue;
                     }
                     sp<Entry> e = c->getEntries().valueAt(ei);
@@ -2872,7 +2866,7 @@
             return amt;
         }
 
-        err = flattenLibraryTable(data, libraryPackages);
+        status_t err = flattenLibraryTable(data, libraryPackages);
         if (err != NO_ERROR) {
             fprintf(stderr, "ERROR: failed to write library table\n");
             return err;
@@ -2928,11 +2922,11 @@
                     }
                     const size_t CN = cl->getEntries().size();
                     for (size_t ci=0; ci<CN; ci++) {
-                        if (filterable && !filter.match(cl->getEntries().keyAt(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))) {
+                            if (filterable && !filter->match(cl->getEntries().keyAt(cj))) {
                                 continue;
                             }
                             typeSpecFlags[ei] |= htodl(
@@ -2974,7 +2968,7 @@
                       config.screenHeightDp,
                       config.layoutDirection));
                       
-                if (filterable && !filter.match(config)) {
+                if (filterable && !filter->match(config)) {
                     continue;
                 }
                 
@@ -3093,7 +3087,7 @@
     }
     
     ssize_t strStart = dest->getSize();
-    err = valueStrings.writeStringBlock(dest);
+    status_t err = valueStrings.writeStringBlock(dest);
     if (err != NO_ERROR) {
         return err;
     }
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index ec8fd175e..a73993c 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -7,8 +7,10 @@
 #ifndef RESOURCE_TABLE_H
 #define RESOURCE_TABLE_H
 
+#include "ConfigDescription.h"
 #include "StringPool.h"
 #include "SourcePos.h"
+#include "ResourceFilter.h"
 
 #include <set>
 #include <map>
@@ -76,37 +78,6 @@
     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);
@@ -182,7 +153,7 @@
     size_t numLocalResources() const;
     bool hasResources() const;
 
-    sp<AaptFile> flatten(Bundle*);
+    sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter);
 
     static inline uint32_t makeResId(uint32_t packageId,
                                      uint32_t typeId,
@@ -223,7 +194,7 @@
     void addLocalization(const String16& name, const String8& locale, const SourcePos& src);
     status_t validateLocalizations(void);
 
-    status_t flatten(Bundle*, const sp<AaptFile>& dest);
+    status_t flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, const sp<AaptFile>& dest);
     status_t flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs);
 
     void writePublicDefinitions(const String16& package, FILE* fp);
diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp
new file mode 100644
index 0000000..e795d818
--- /dev/null
+++ b/tools/aapt/tests/AaptConfig_test.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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 <utils/String8.h>
+#include <gtest/gtest.h>
+
+#include "AaptConfig.h"
+#include "ConfigDescription.h"
+#include "TestHelper.h"
+
+using android::String8;
+
+static ::testing::AssertionResult TestParse(const String8& input, ConfigDescription* config=NULL) {
+    if (AaptConfig::parse(String8(input), config)) {
+        return ::testing::AssertionSuccess() << input << " was successfully parsed";
+    }
+    return ::testing::AssertionFailure() << input << " could not be parsed";
+}
+
+static ::testing::AssertionResult TestParse(const char* input, ConfigDescription* config=NULL) {
+    return TestParse(String8(input), config);
+}
+
+TEST(AaptConfigTest, ParseFailWhenQualifiersAreOutOfOrder) {
+    EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
+    EXPECT_FALSE(TestParse("land-en"));
+    EXPECT_FALSE(TestParse("hdpi-320dpi"));
+}
+
+TEST(AaptConfigTest, ParseFailWhenQualifiersAreNotMatched) {
+    EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
+}
+
+TEST(AaptConfigTest, ParseFailWhenQualifiersHaveTrailingDash) {
+    EXPECT_FALSE(TestParse("en-sw600dp-land-"));
+}
+
+TEST(AaptConfigTest, ParseBasicQualifiers) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("", &config));
+    EXPECT_EQ(String8(""), config.toString());
+
+    EXPECT_TRUE(TestParse("fr-land", &config));
+    EXPECT_EQ(String8("fr-land"), config.toString());
+
+    EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
+                "xhdpi-keyssoft-qwerty-navexposed-nonav", &config));
+    EXPECT_EQ(String8("mcc310-pl-sw720dp-normal-long-port-night-"
+                "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString());
+}
+
+TEST(AaptConfigTest, ParseLocales) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("en-rUS", &config));
+    EXPECT_EQ(String8("en-US"), config.toString());
+}
+
+TEST(AaptConfigTest, ParseQualifierAddedInApi13) {
+    ConfigDescription config;
+    EXPECT_TRUE(TestParse("sw600dp", &config));
+    EXPECT_EQ(String8("sw600dp-v13"), config.toString());
+
+    EXPECT_TRUE(TestParse("sw600dp-v8", &config));
+    EXPECT_EQ(String8("sw600dp-v13"), config.toString());
+}
diff --git a/tools/aapt/tests/AaptGroupEntry_test.cpp b/tools/aapt/tests/AaptGroupEntry_test.cpp
new file mode 100644
index 0000000..7348a08
--- /dev/null
+++ b/tools/aapt/tests/AaptGroupEntry_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 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 <utils/String8.h>
+#include <gtest/gtest.h>
+
+#include "AaptAssets.h"
+#include "ResourceFilter.h"
+#include "TestHelper.h"
+
+using android::String8;
+
+static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const String8& dirName,
+        String8* outType) {
+    if (entry.initFromDirName(dirName, outType)) {
+        return ::testing::AssertionSuccess() << dirName << " was successfully parsed";
+    }
+    return ::testing::AssertionFailure() << dirName << " could not be parsed";
+}
+
+static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const char* input,
+        String8* outType) {
+    return TestParse(entry, String8(input), outType);
+}
+
+TEST(AaptGroupEntryTest, ParseNoQualifier) {
+    AaptGroupEntry entry;
+    String8 type;
+    EXPECT_TRUE(TestParse(entry, "menu", &type));
+    EXPECT_EQ(String8("menu"), type);
+}
+
+TEST(AaptGroupEntryTest, ParseCorrectType) {
+    AaptGroupEntry entry;
+    String8 type;
+    EXPECT_TRUE(TestParse(entry, "anim", &type));
+    EXPECT_EQ(String8("anim"), type);
+
+    EXPECT_TRUE(TestParse(entry, "animator", &type));
+    EXPECT_EQ(String8("animator"), type);
+}
diff --git a/tools/aapt/tests/ResourceFilter_test.cpp b/tools/aapt/tests/ResourceFilter_test.cpp
new file mode 100644
index 0000000..30697bb
--- /dev/null
+++ b/tools/aapt/tests/ResourceFilter_test.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+#include <utils/String8.h>
+#include <gtest/gtest.h>
+
+#include "AaptConfig.h"
+#include "ResourceFilter.h"
+#include "ConfigDescription.h"
+
+using android::String8;
+
+// In this context, 'Axis' represents a particular field in the configuration,
+// such as language or density.
+
+TEST(WeakResourceFilterTest, EmptyFilterMatchesAnything) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("")));
+
+    ConfigDescription config;
+    config.density = 320;
+
+    EXPECT_TRUE(filter.match(config));
+
+    config.language[0] = 'f';
+    config.language[1] = 'r';
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithUnrelatedAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.density = 320;
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.language[0] = 'f';
+    config.language[1] = 'r';
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.language[0] = 'f';
+    config.language[1] = 'r';
+    config.density = 320;
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("fr")));
+
+    ConfigDescription config;
+    config.language[0] = 'd';
+    config.language[1] = 'e';
+
+    EXPECT_FALSE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("de-rDE")));
+
+    ConfigDescription config;
+    config.language[0] = 'd';
+    config.language[1] = 'e';
+
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, ParsesStandardLocaleOnlyString) {
+    WeakResourceFilter filter;
+    EXPECT_EQ(NO_ERROR, filter.parse(String8("de_DE")));
+}
+
+TEST(WeakResourceFilterTest, IgnoresVersion) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("normal-v4")));
+
+    ConfigDescription config;
+    config.smallestScreenWidthDp = 600;
+    config.version = 13;
+
+    // The configs don't match on any axis besides version, which should be ignored.
+    EXPECT_TRUE(filter.match(config));
+}
+
+TEST(WeakResourceFilterTest, MatchesConfigWithRegion) {
+    WeakResourceFilter filter;
+    ASSERT_EQ(NO_ERROR, filter.parse(String8("kok,kok_IN,kok_419")));
+
+    ConfigDescription config;
+    AaptLocaleValue val;
+    ASSERT_TRUE(val.initFromFilterString(String8("kok_IN")));
+    val.writeTo(&config);
+
+    EXPECT_TRUE(filter.match(config));
+}
+
diff --git a/tools/aapt/tests/TestHelper.h b/tools/aapt/tests/TestHelper.h
new file mode 100644
index 0000000..79174832
--- /dev/null
+++ b/tools/aapt/tests/TestHelper.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 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 __TEST_HELPER_H
+#define __TEST_HELPER_H
+
+#include <utils/String8.h>
+
+namespace android {
+
+/**
+ * Stream operator for nicely printing String8's in gtest output.
+ */
+inline std::ostream& operator<<(std::ostream& stream, const String8& str) {
+    return stream << str.string();
+}
+
+}
+
+#endif