AAPT support for feature splits

This change allows the developer to add a base package for
which to build a feature split. The generated resource types
will begin after the base APK's defined types so as not
to collide or override resources.

Multiple features can be generated by first choosing an
arbitrary order for the features. Then for each feature,
the base APK and any preceding features are specified
with the --feature-of flags.

So with a base APK 'A' and features, 'B', and 'C',
'B' would be built with

aapt package [...] --feature-of A [...]

and 'C' would be built with

aapt package [...] --feature-of A --feature-of B [...]

Change-Id: I1be66e3f8df9a737b21c71f8a93685376c7e6780
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index ac54638..cf1d4fd 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -1713,12 +1713,49 @@
     return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
 }
 
-ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage)
-    : mAssetsPackage(assetsPackage), mNextPackageId(1), mHaveAppPackage(false),
-      mIsAppPackage(!bundle->getExtending()), mIsSharedLibrary(bundle->getBuildSharedLibrary()),
-      mNumLocal(0),
-      mBundle(bundle)
+ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
+    : mAssetsPackage(assetsPackage)
+    , mPackageType(type)
+    , mTypeIdOffset(0)
+    , mNumLocal(0)
+    , mBundle(bundle)
 {
+    ssize_t packageId = -1;
+    switch (mPackageType) {
+        case App:
+        case AppFeature:
+            packageId = 0x7f;
+            break;
+
+        case System:
+            packageId = 0x01;
+            break;
+
+        case SharedLibrary:
+            packageId = 0x00;
+            break;
+
+        default:
+            assert(0);
+            break;
+    }
+    sp<Package> package = new Package(mAssetsPackage, packageId);
+    mPackages.add(assetsPackage, package);
+    mOrderedPackages.add(package);
+
+    // Every resource table always has one first entry, the bag attributes.
+    const SourcePos unknown(String8("????"), 0);
+    getType(mAssetsPackage, String16("attr"), unknown);
+}
+
+static uint32_t findLargestTypeIdForPackage(const ResTable& table, const String16& packageName) {
+    const size_t basePackageCount = table.getBasePackageCount();
+    for (size_t i = 0; i < basePackageCount; i++) {
+        if (packageName == table.getBasePackageName(i)) {
+            return table.getLastTypeIdForPackage(i);
+        }
+    }
+    return 0;
 }
 
 status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
@@ -1728,59 +1765,22 @@
         return err;
     }
 
-    // For future reference to included resources.
     mAssets = assets;
+    mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);
 
-    const ResTable& incl = assets->getIncludedResources();
-
-    // Retrieve all the packages.
-    const size_t N = incl.getBasePackageCount();
-    for (size_t phase=0; phase<2; phase++) {
-        for (size_t i=0; i<N; i++) {
-            const String16 name = incl.getBasePackageName(i);
-            uint32_t id = incl.getBasePackageId(i);
-
-            // First time through: only add base packages (id
-            // is not 0); second time through add the other
-            // packages.
-            if (phase != 0) {
-                if (id != 0) {
-                    // Skip base packages -- already one.
-                    id = 0;
-                } else {
-                    // Assign a dynamic id.
-                    id = mNextPackageId;
-                }
-            } else if (id != 0) {
-                if (id == 127) {
-                    if (mHaveAppPackage) {
-                        fprintf(stderr, "Included resources have two application packages!\n");
-                        return UNKNOWN_ERROR;
-                    }
-                    mHaveAppPackage = true;
-                }
-                if (mNextPackageId > id) {
-                    fprintf(stderr, "Included base package ID %d already in use!\n", id);
-                    return UNKNOWN_ERROR;
-                }
-            }
-            if (id != 0) {
-                NOISY(fprintf(stderr, "Including package %s with ID=%d\n",
-                             String8(name).string(), id));
-                sp<Package> p = new Package(name, id);
-                mPackages.add(name, p);
-                mOrderedPackages.add(p);
-
-                if (id >= mNextPackageId) {
-                    mNextPackageId = id+1;
-                }
-            }
+    const String8& featureAfter = bundle->getFeatureAfterPackage();
+    if (!featureAfter.isEmpty()) {
+        AssetManager featureAssetManager;
+        if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
+            fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
+                    featureAfter.string());
+            return UNKNOWN_ERROR;
         }
-    }
 
-    // Every resource table always has one first entry, the bag attributes.
-    const SourcePos unknown(String8("????"), 0);
-    sp<Type> attr = getType(mAssetsPackage, String16("attr"), unknown);
+        const ResTable& featureTable = featureAssetManager.getResources(false);
+        mTypeIdOffset = max(mTypeIdOffset,
+                findLargestTypeIdForPackage(featureTable, mAssetsPackage)); 
+    }
 
     return NO_ERROR;
 }
@@ -1820,24 +1820,16 @@
                                  const int32_t format,
                                  const bool overwrite)
 {
-    // Check for adding entries in other packages...  for now we do
-    // nothing.  We need to do the right thing here to support skinning.
     uint32_t rid = mAssets->getIncludedResources()
         .identifierForName(name.string(), name.size(),
                            type.string(), type.size(),
                            package.string(), package.size());
     if (rid != 0) {
-        return NO_ERROR;
+        sourcePos.error("Resource entry %s/%s is already defined in package %s.",
+                String8(type).string(), String8(name).string(), String8(package).string());
+        return UNKNOWN_ERROR;
     }
     
-#if 0
-    if (name == String16("left")) {
-        printf("Adding entry left: file=%s, line=%d, type=%s, value=%s\n",
-               sourcePos.file.string(), sourcePos.line, String8(type).string(),
-               String8(value).string());
-    }
-#endif
-
     sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
                            params, doSetIndex);
     if (e == NULL) {
@@ -1868,15 +1860,11 @@
                        type.string(), type.size(),
                        package.string(), package.size());
     if (rid != 0) {
-        return NO_ERROR;
+        sourcePos.error("Resource entry %s/%s is already defined in package %s.",
+                String8(type).string(), String8(name).string(), String8(package).string());
+        return UNKNOWN_ERROR;
     }
-    
-#if 0
-    if (name == String16("left")) {
-        printf("Adding bag left: file=%s, line=%d, type=%s\n",
-               sourcePos.file.striing(), sourcePos.line, String8(type).string());
-    }
-#endif
+
     if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) {
         bool canAdd = false;
         sp<Package> p = mPackages.valueFor(package);
@@ -2117,9 +2105,6 @@
     uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic);
     if (id != 0) return id;     // cache hit
 
-    sp<Package> p = mPackages.valueFor(package);
-    if (p == NULL) return 0;
-
     // First look for this in the included resources...
     uint32_t specFlags = 0;
     uint32_t rid = mAssets->getIncludedResources()
@@ -2134,13 +2119,11 @@
             }
         }
         
-        if (Res_INTERNALID(rid)) {
-            return ResourceIdCache::store(package, type, name, onlyPublic, rid);
-        }
-        return ResourceIdCache::store(package, type, name, onlyPublic,
-                Res_MAKEID(p->getAssignedId()-1, Res_GETTYPE(rid), Res_GETENTRY(rid)));
+        return ResourceIdCache::store(package, type, name, onlyPublic, rid);
     }
 
+    sp<Package> p = mPackages.valueFor(package);
+    if (p == NULL) return 0;
     sp<Type> t = p->getTypes().valueFor(type);
     if (t == NULL) return 0;
     sp<ConfigList> c =  t->getConfigs().valueFor(name);
@@ -2294,9 +2277,12 @@
     }
 
     if (mAssetsPackage != package) {
-        mCurrentXmlPos.warning("creating resource for external package %s: %s/%s.",
+        mCurrentXmlPos.error("creating resource for external package %s: %s/%s.",
                 String8(package).string(), String8(type).string(), String8(name).string());
-        mCurrentXmlPos.printf("This will be an error in a future version of AAPT.");
+        if (package == String16("android")) {
+            mCurrentXmlPos.printf("did you mean to use @+id instead of @+android:id?");
+        }
+        return 0;
     }
 
     String16 value("false");
@@ -2479,6 +2465,7 @@
             continue;
         }
 
+        // This has no sense for packages being built as AppFeature (aka with a non-zero offset).
         status_t err = p->applyPublicTypeOrder();
         if (err != NO_ERROR && firstError == NO_ERROR) {
             firstError = err;
@@ -2512,6 +2499,11 @@
             }
         }
 
+        uint32_t typeIdOffset = 0;
+        if (mPackageType == AppFeature && p->getName() == mAssetsPackage) {
+            typeIdOffset = mTypeIdOffset;
+        }
+
         const SourcePos unknown(String8("????"), 0);
         sp<Type> attr = p->getType(String16("attr"), unknown);
 
@@ -2527,7 +2519,7 @@
             }
 
             const size_t N = t->getOrderedConfigs().size();
-            t->setIndex(ti+1);
+            t->setIndex(ti + 1 + typeIdOffset);
 
             LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
                                 "First type is not attr!");
@@ -2599,7 +2591,7 @@
                 if (rid == 0) {
                     return UNKNOWN_ERROR;
                 }
-                if (Res_GETPACKAGE(rid) == (size_t)(p->getAssignedId()-1)) {
+                if (Res_GETPACKAGE(rid) + 1 == p->getAssignedId()) {
                     typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
                     
                     String16 comment(c->getComment());
@@ -2608,11 +2600,6 @@
                     //        String8(c->getName()).string(), String8(comment).string());
                     comment = c->getTypeComment();
                     typeSymbols->appendTypeComment(String8(c->getName()), comment);
-                } else {
-#if 0
-                    printf("**** NO MATCH: 0x%08x vs 0x%08x\n",
-                           Res_GETPACKAGE(rid), p->getAssignedId());
-#endif
                 }
             }
         }
@@ -2749,7 +2736,7 @@
             }
             continue;
         } else if (p->getAssignedId() == 0x00) {
-            if (!bundle->getBuildSharedLibrary()) {
+            if (mPackageType != SharedLibrary) {
                 fprintf(stderr, "ERROR: Package %s can not have ID=0x00 unless building a shared library.",
                         String8(p->getName()).string());
                 return UNKNOWN_ERROR;
@@ -2761,15 +2748,24 @@
         StringPool typeStrings(useUTF8);
         StringPool keyStrings(useUTF8);
 
+        ssize_t stringsAdded = 0;
         const size_t N = p->getOrderedTypes().size();
         for (size_t ti=0; ti<N; ti++) {
             sp<Type> t = p->getOrderedTypes().itemAt(ti);
             if (t == NULL) {
                 typeStrings.add(String16("<empty>"), false);
+                stringsAdded++;
                 continue;
             }
+
+            while (stringsAdded < t->getIndex() - 1) {
+                typeStrings.add(String16("<empty>"), false);
+                stringsAdded++;
+            }
+
             const String16 typeName(t->getName());
             typeStrings.add(typeName, false);
+            stringsAdded++;
 
             // This is a hack to tweak the sorting order of the final strings,
             // to put stuff that is generally not language-specific first.
@@ -2862,7 +2858,7 @@
         memset(header, 0, sizeof(*header));
         header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
         header->header.headerSize = htods(sizeof(*header));
-        header->id = htodl(p->getAssignedId());
+        header->id = htodl(static_cast<uint32_t>(p->getAssignedId()));
         strcpy16_htod(header->name, p->getName().string());
 
         // Write the string blocks.
@@ -2902,7 +2898,9 @@
             LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"),
                                 "Type name %s not found",
                                 String8(typeName).string());
-
+            if (t == NULL) {
+                continue;
+            }
             const bool filterable = (typeName != mipmap16);
 
             const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
@@ -3062,21 +3060,25 @@
                 tHeader->header.size = htodl(data->getSize()-typeStart);
             }
 
-            bool missing_entry = false;
-            const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
-                    "error" : "warning";
-            for (size_t i = 0; i < N; ++i) {
-                if (!validResources[i]) {
-                    sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
-                    fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
-                            String8(typeName).string(), String8(c->getName()).string(),
-                            Res_MAKEID(p->getAssignedId() - 1, ti, i));
-                    missing_entry = true;
+            // If we're building splits, then each invocation of the flattening
+            // step will have 'missing' entries. Don't warn/error for this case.
+            if (bundle->getSplitConfigurations().isEmpty()) {
+                bool missing_entry = false;
+                const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
+                        "error" : "warning";
+                for (size_t i = 0; i < N; ++i) {
+                    if (!validResources[i]) {
+                        sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
+                        fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
+                                String8(typeName).string(), String8(c->getName()).string(),
+                                Res_MAKEID(p->getAssignedId() - 1, ti, i));
+                        missing_entry = true;
+                    }
                 }
-            }
-            if (bundle->getErrorOnMissingConfigEntry() && missing_entry) {
-                fprintf(stderr, "Error: Missing entries, quit!\n");
-                return NOT_ENOUGH_DATA;
+                if (bundle->getErrorOnMissingConfigEntry() && missing_entry) {
+                    fprintf(stderr, "Error: Missing entries, quit!\n");
+                    return NOT_ENOUGH_DATA;
+                }
             }
         }
 
@@ -3819,8 +3821,8 @@
     return hasError ? UNKNOWN_ERROR : NO_ERROR;
 }
 
-ResourceTable::Package::Package(const String16& name, ssize_t includedId)
-    : mName(name), mIncludedId(includedId),
+ResourceTable::Package::Package(const String16& name, size_t packageId)
+    : mName(name), mPackageId(packageId),
       mTypeStringsMapping(0xffffffff),
       mKeyStringsMapping(0xffffffff)
 {
@@ -3945,26 +3947,10 @@
 
 sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
 {
-    sp<Package> p = mPackages.valueFor(package);
-    if (p == NULL) {
-        if (mIsAppPackage) {
-            if (mHaveAppPackage) {
-                fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n"
-                                "Use -x to create extended resources.\n");
-                return NULL;
-            }
-            mHaveAppPackage = true;
-            p = new Package(package, mIsSharedLibrary ? 0 : 127);
-        } else {
-            p = new Package(package, mNextPackageId);
-        }
-        //printf("*** NEW PACKAGE: \"%s\" id=%d\n",
-        //       String8(package).string(), p->getAssignedId());
-        mPackages.add(package, p);
-        mOrderedPackages.add(p);
-        mNextPackageId++;
+    if (package != mAssetsPackage) {
+        return NULL;
     }
-    return p;
+    return mPackages.valueFor(package);
 }
 
 sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
@@ -3997,11 +3983,10 @@
 sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
                                                        const ResTable_config* config) const
 {
-    int pid = Res_GETPACKAGE(resID)+1;
+    size_t pid = Res_GETPACKAGE(resID)+1;
     const size_t N = mOrderedPackages.size();
-    size_t i;
     sp<Package> p;
-    for (i=0; i<N; i++) {
+    for (size_t i = 0; i < N; i++) {
         sp<Package> check = mOrderedPackages[i];
         if (check->getAssignedId() == pid) {
             p = check;