Filter products during compile phase

Unfortunately there is no good way to deal with products in the link phase.
Products are like preprocessor defines in that they are processed early
and change the composition of the compiled unit.

Change-Id: I6d5e15ef60d29df8e83e059ba857c09333993779
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index d20ae1b..7ea26b3 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -51,12 +51,6 @@
         mMessage << value;
         return *this;
     }
-/*
-    template <typename T> DiagMessage& operator<<(
-            const ::std::function<::std::ostream&(::std::ostream&)>& f) {
-        f(mMessage);
-        return *this;
-    }*/
 
     DiagMessageActual build() const {
         return DiagMessageActual{ mSource, mMessage.str() };
@@ -72,6 +66,8 @@
 };
 
 struct StdErrDiagnostics : public IDiagnostics {
+    size_t mNumErrors = 0;
+
     void emit(const DiagMessage& msg, const char* tag) {
         DiagMessageActual actual = msg.build();
         if (!actual.source.path.empty()) {
@@ -81,7 +77,10 @@
     }
 
     void error(const DiagMessage& msg) override {
-        emit(msg, "error: ");
+        if (mNumErrors < 20) {
+            emit(msg, "error: ");
+        }
+        mNumErrors++;
     }
 
     void warn(const DiagMessage& msg) override {
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 31fe298..7ef1897 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -78,6 +78,9 @@
     ResourceType type;
     std::u16string entry;
 
+    ResourceName() = default;
+    ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e);
+
     bool isValid() const;
     bool operator<(const ResourceName& rhs) const;
     bool operator==(const ResourceName& rhs) const;
@@ -226,6 +229,10 @@
 // ResourceName implementation.
 //
 
+inline ResourceName::ResourceName(const StringPiece16& p, ResourceType t, const StringPiece16& e) :
+        package(p.toString()), type(t), entry(e.toString()) {
+}
+
 inline bool ResourceName::isValid() const {
     return !package.empty() && !entry.empty();
 }
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 5e5fc53..63629f0 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -49,8 +49,9 @@
 }
 
 ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
-                               const ConfigDescription& config) :
-        mDiag(diag), mTable(table), mSource(source), mConfig(config) {
+                               const ConfigDescription& config,
+                               const ResourceParserOptions& options) :
+        mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {
 }
 
 /**
@@ -157,7 +158,62 @@
     return !error;
 }
 
+static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) {
+    assert(parser->getEvent() == XmlPullParser::Event::kStartElement);
+
+    if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) {
+        if (!productToMatch) {
+            if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
+                // We didn't specify a product and this is not a default product, so skip.
+                return true;
+            }
+        } else {
+            if (productToMatch && maybeProduct.value() != productToMatch.value()) {
+                // We specified a product, but they don't match.
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+/**
+ * A parsed resource ready to be added to the ResourceTable.
+ */
+struct ParsedResource {
+    ResourceName name;
+    Source source;
+    ResourceId id;
+    bool markPublic = false;
+    std::unique_ptr<Value> value;
+    std::list<ParsedResource> childResources;
+};
+
+// Recursively adds resources to the ResourceTable.
+static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
+                                IDiagnostics* diag, ParsedResource* res) {
+    if (res->markPublic && !table->markPublic(res->name, res->id, res->source, diag)) {
+        return false;
+    }
+
+    if (!res->value) {
+        return true;
+    }
+
+    if (!table->addResource(res->name, res->id, config, res->source, std::move(res->value), diag)) {
+        return false;
+    }
+
+    bool error = false;
+    for (ParsedResource& child : res->childResources) {
+        error |= !addResourcesToTable(table, config, diag, &child);
+    }
+    return !error;
+}
+
 bool ResourceParser::parseResources(XmlPullParser* parser) {
+    std::set<ResourceName> strippedResources;
+
     bool error = false;
     std::u16string comment;
     const size_t depth = parser->getDepth();
@@ -198,9 +254,8 @@
             continue;
         }
 
-        // Copy because our iterator will go out of scope when
-        // we parse more XML.
-        std::u16string name = maybeName.value().toString();
+        // Check if we should skip this product.
+        const bool stripResource = shouldStripResource(parser, mOptions.product);
 
         if (elementName == u"item") {
             // Items simply have their type encoded in the type attribute.
@@ -214,48 +269,85 @@
             }
         }
 
-        if (elementName == u"id") {
-            error |= !mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, name },
-                                          {}, mSource.withLine(parser->getLineNumber()),
-                                          util::make_unique<Id>(), mDiag);
+        ParsedResource parsedResource;
+        parsedResource.name.entry = maybeName.value().toString();
+        parsedResource.source = mSource.withLine(parser->getLineNumber());
 
+        bool result = true;
+        if (elementName == u"id") {
+            parsedResource.name.type = ResourceType::kId;
+            parsedResource.value = util::make_unique<Id>();
         } else if (elementName == u"string") {
-            error |= !parseString(parser, ResourceNameRef{ {}, ResourceType::kString, name });
+            parsedResource.name.type = ResourceType::kString;
+            result = parseString(parser, &parsedResource);
         } else if (elementName == u"color") {
-            error |= !parseColor(parser, ResourceNameRef{ {}, ResourceType::kColor, name });
+            parsedResource.name.type = ResourceType::kColor;
+            result = parseColor(parser, &parsedResource);
         } else if (elementName == u"drawable") {
-            error |= !parseColor(parser, ResourceNameRef{ {}, ResourceType::kDrawable, name });
+            parsedResource.name.type = ResourceType::kDrawable;
+            result = parseColor(parser, &parsedResource);
         } else if (elementName == u"bool") {
-            error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kBool, name });
+            parsedResource.name.type = ResourceType::kBool;
+            result = parsePrimitive(parser, &parsedResource);
         } else if (elementName == u"integer") {
-            error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kInteger, name });
+            parsedResource.name.type = ResourceType::kInteger;
+            result = parsePrimitive(parser, &parsedResource);
         } else if (elementName == u"dimen") {
-            error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kDimen, name });
+            parsedResource.name.type = ResourceType::kDimen;
+            result = parsePrimitive(parser, &parsedResource);
         } else if (elementName == u"style") {
-            error |= !parseStyle(parser, ResourceNameRef{ {}, ResourceType::kStyle, name });
+            parsedResource.name.type = ResourceType::kStyle;
+            result = parseStyle(parser, &parsedResource);
         } else if (elementName == u"plurals") {
-            error |= !parsePlural(parser, ResourceNameRef{ {}, ResourceType::kPlurals, name });
+            parsedResource.name.type = ResourceType::kPlurals;
+            result = parsePlural(parser, &parsedResource);
         } else if (elementName == u"array") {
-            error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name },
-                                 android::ResTable_map::TYPE_ANY);
+            parsedResource.name.type = ResourceType::kArray;
+            result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_ANY);
         } else if (elementName == u"string-array") {
-            error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name },
-                                 android::ResTable_map::TYPE_STRING);
+            parsedResource.name.type = ResourceType::kArray;
+            result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING);
         } else if (elementName == u"integer-array") {
-            error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name },
-                                 android::ResTable_map::TYPE_INTEGER);
-        } else if (elementName == u"public") {
-            error |= !parsePublic(parser, name);
+            parsedResource.name.type = ResourceType::kIntegerArray;
+            result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER);
         } else if (elementName == u"declare-styleable") {
-            error |= !parseDeclareStyleable(parser,
-                                            ResourceNameRef{ {}, ResourceType::kStyleable, name });
+            parsedResource.name.type = ResourceType::kStyleable;
+            result = parseDeclareStyleable(parser, &parsedResource);
         } else if (elementName == u"attr") {
-            error |= !parseAttr(parser, ResourceNameRef{ {}, ResourceType::kAttr, name });
+            parsedResource.name.type = ResourceType::kAttr;
+            result = parseAttr(parser, &parsedResource);
+        } else if (elementName == u"public") {
+            result = parsePublic(parser, &parsedResource);
         } else {
             mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
                         << "unknown resource type '" << elementName << "'");
         }
+
+        if (result) {
+            // We successfully parsed the resource.
+
+            if (stripResource) {
+                // Record that we stripped out this resource name.
+                // We will check that at least one variant of this resource was included.
+                strippedResources.insert(parsedResource.name);
+            } else {
+                error |= !addResourcesToTable(mTable, mConfig, mDiag, &parsedResource);
+            }
+        } else {
+            error = true;
+        }
     }
+
+    // Check that we included at least one variant of each stripped resource.
+    for (const ResourceName& strippedResource : strippedResources) {
+        if (!mTable->findResource(strippedResource)) {
+            // Failed to find the resource.
+            mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' "
+                         "was filtered out but no product variant remains");
+            error = true;
+        }
+    }
+
     return !error;
 }
 
@@ -322,53 +414,43 @@
     return {};
 }
 
-bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     // TODO(adamlesinski): Read "untranslateable" attribute.
 
-    if (Maybe<StringPiece16> maybeProduct = findAttribute(parser, u"product")) {
-        if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
-            // TODO(adamlesinski): Actually match product.
-            return true;
-        }
-    }
-
-    std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING,
-                                                   kNoRawString);
-    if (!processedItem) {
+    outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
+    if (!outResource->value) {
         mDiag->error(DiagMessage(source) << "not a valid string");
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(processedItem),
-                               mDiag);
+    return true;
 }
 
-bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
-    if (!item) {
+    outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
+    if (!outResource->value) {
         mDiag->error(DiagMessage(source) << "invalid color");
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(item),
-                               mDiag);
+    return true;
 }
 
-bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     uint32_t typeMask = 0;
-    switch (resourceName.type) {
+    switch (outResource->name.type) {
     case ResourceType::kInteger:
         typeMask |= android::ResTable_map::TYPE_INTEGER;
         break;
 
     case ResourceType::kDimen:
         typeMask |= android::ResTable_map::TYPE_DIMENSION
-        | android::ResTable_map::TYPE_FLOAT
-        | android::ResTable_map::TYPE_FRACTION;
+                  | android::ResTable_map::TYPE_FLOAT
+                  | android::ResTable_map::TYPE_FRACTION;
         break;
 
     case ResourceType::kBool:
@@ -380,16 +462,15 @@
         break;
     }
 
-    std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
-    if (!item) {
-        mDiag->error(DiagMessage(source) << "invalid " << resourceName.type);
+    outResource->value = parseXml(parser, typeMask, kNoRawString);
+    if (!outResource->value) {
+        mDiag->error(DiagMessage(source) << "invalid " << outResource->name.type);
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(item),
-                               mDiag);
+    return true;
 }
 
-bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) {
+bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
@@ -405,27 +486,28 @@
         return false;
     }
 
-    ResourceNameRef resourceName { {}, *parsedType, name };
-    ResourceId resourceId;
+    outResource->name.type = *parsedType;
 
     if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
         android::Res_value val;
         bool result = android::ResTable::stringToInt(maybeId.value().data(),
                                                      maybeId.value().size(), &val);
-        resourceId.id = val.data;
+        ResourceId resourceId(val.data);
         if (!result || !resourceId.isValid()) {
             mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value()
                          << "' in <public>");
             return false;
         }
+        outResource->id = resourceId;
     }
 
     if (*parsedType == ResourceType::kId) {
         // An ID marked as public is also the definition of an ID.
-        mTable->addResource(resourceName, {}, source, util::make_unique<Id>(),
-                            mDiag);
+        outResource->value = util::make_unique<Id>();
     }
-    return mTable->markPublic(resourceName, resourceId, source, mDiag);
+
+    outResource->markPublic = true;
+    return true;
 }
 
 static uint32_t parseFormatType(const StringPiece16& piece) {
@@ -455,20 +537,13 @@
     return mask;
 }
 
-bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
-    const Source source = mSource.withLine(parser->getLineNumber());
-    ResourceName actualName = resourceName.toResourceName();
-    std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false);
-    if (!attr) {
-        return false;
-    }
-    return mTable->addResource(actualName, mConfig, source, std::move(attr),
-                               mDiag);
+
+bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
+    outResource->source = mSource.withLine(parser->getLineNumber());
+    return parseAttrImpl(parser, outResource, false);
 }
 
-std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
-                                                         ResourceName* resourceName,
-                                                         bool weak) {
+bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) {
     uint32_t typeMask = 0;
 
     Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
@@ -477,7 +552,7 @@
         if (typeMask == 0) {
             mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                          << "invalid attribute format '" << maybeFormat.value() << "'");
-            return {};
+            return false;
         }
     }
 
@@ -486,10 +561,10 @@
     // No format attribute is allowed.
     if (weak && !maybeFormat) {
         StringPiece16 package, type, name;
-        ResourceUtils::extractResourceName(resourceName->entry, &package, &type, &name);
+        ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name);
         if (type.empty() && !package.empty()) {
-            resourceName->package = package.toString();
-            resourceName->entry = name.toString();
+            outResource->name.package = package.toString();
+            outResource->name.entry = name.toString();
         }
     }
 
@@ -526,14 +601,12 @@
             }
 
             if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
-                if (mTable->addResource(s.value().symbol.name.value(), mConfig,
-                                        mSource.withLine(parser->getLineNumber()),
-                                        util::make_unique<Id>(),
-                                        mDiag)) {
-                    items.push_back(std::move(s.value()));
-                } else {
-                    error = true;
-                }
+                ParsedResource childResource;
+                childResource.name = s.value().symbol.name.value();
+                childResource.source = mSource.withLine(parser->getLineNumber());
+                childResource.value = util::make_unique<Id>();
+                outResource->childResources.push_back(std::move(childResource));
+                items.push_back(std::move(s.value()));
             } else {
                 error = true;
             }
@@ -548,13 +621,14 @@
     }
 
     if (error) {
-        return {};
+        return false;
     }
 
     std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
     attr->symbols.swap(items);
     attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
-    return attr;
+    outResource->value = std::move(attr);
+    return true;
 }
 
 Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
@@ -582,8 +656,8 @@
     }
 
     return Attribute::Symbol{
-            Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
-            val.data };
+        Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
+                val.data };
 }
 
 static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
@@ -604,7 +678,7 @@
     }
 
     return ResourceName{ package.toString(), ResourceType::kAttr,
-                         name.empty() ? str.toString() : name.toString() };
+        name.empty() ? str.toString() : name.toString() };
 }
 
 
@@ -637,7 +711,7 @@
     return true;
 }
 
-bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Style> style = util::make_unique<Style>();
 
@@ -660,12 +734,12 @@
 
     } else {
         // No parent was specified, so try inferring it from the style name.
-        std::u16string styleName = resourceName.entry.toString();
+        std::u16string styleName = outResource->name.entry;
         size_t pos = styleName.find_last_of(u'.');
         if (pos != std::string::npos) {
             style->parentInferred = true;
-            style->parent = Reference(ResourceName{
-                {}, ResourceType::kStyle, styleName.substr(0, pos) });
+            style->parent = Reference(
+                    ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
         }
     }
 
@@ -697,11 +771,12 @@
     if (error) {
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(style),
-                               mDiag);
+
+    outResource->value = std::move(style);
+    return true;
 }
 
-bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName,
+bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource,
                                 uint32_t typeMask) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Array> array = util::make_unique<Array>();
@@ -741,11 +816,12 @@
     if (error) {
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(array),
-                               mDiag);
+
+    outResource->value = std::move(array);
+    return true;
 }
 
-bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) {
+bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
 
@@ -816,11 +892,12 @@
     if (error) {
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(plural), mDiag);
+
+    outResource->value = std::move(plural);
+    return true;
 }
 
-bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
-                                           const ResourceNameRef& resourceName) {
+bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
 
@@ -844,22 +921,17 @@
                 continue;
             }
 
-            // Copy because our iterator will be invalidated.
-            ResourceName attrResourceName = { {}, ResourceType::kAttr, attrIter->value };
+            ParsedResource childResource;
+            childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
+            childResource.source = mSource.withLine(parser->getLineNumber());
 
-            std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &attrResourceName, true);
-            if (!attr) {
+            if (!parseAttrImpl(parser, &childResource, true)) {
                 error = true;
                 continue;
             }
 
-            styleable->entries.emplace_back(attrResourceName);
-
-            // Add the attribute to the resource table. Since it is weakly defined,
-            // it won't collide.
-            error |= !mTable->addResource(attrResourceName, mConfig,
-                                          mSource.withLine(parser->getLineNumber()),
-                                          std::move(attr), mDiag);
+            styleable->entries.push_back(Reference(childResource.name));
+            outResource->childResources.push_back(std::move(childResource));
 
         } else if (elementNamespace.empty() &&
                 (elementName == u"skip" || elementName == u"eat-comment")) {
@@ -875,7 +947,9 @@
     if (error) {
         return false;
     }
-    return mTable->addResource(resourceName, mConfig, source, std::move(styleable), mDiag);
+
+    outResource->value = std::move(styleable);
+    return true;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 514e558..5ccd47f 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -31,13 +31,24 @@
 
 namespace aapt {
 
+struct ParsedResource;
+
+struct ResourceParserOptions {
+    /**
+     * Optional product name by which to filter resources.
+     * This is like a preprocessor definition in that we strip out resources
+     * that don't match before we compile them.
+     */
+    Maybe<std::u16string> product;
+};
+
 /*
  * Parses an XML file for resources and adds them to a ResourceTable.
  */
 class ResourceParser {
 public:
     ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
-                   const ConfigDescription& config);
+                   const ConfigDescription& config, const ResourceParserOptions& options = {});
 
     ResourceParser(const ResourceParser&) = delete; // No copy.
 
@@ -62,25 +73,24 @@
     std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
 
     bool parseResources(XmlPullParser* parser);
-    bool parseString(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
-    bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
-                                             ResourceName* resourceName,
-                                             bool weak);
+    bool parseString(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
     Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
-    bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    bool parseStyle(XmlPullParser* parser, ParsedResource* outResource);
     bool parseStyleItem(XmlPullParser* parser, Style* style);
-    bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName);
-    bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask);
-    bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName);
+    bool parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseArray(XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
+    bool parsePlural(XmlPullParser* parser, ParsedResource* outResource);
 
     IDiagnostics* mDiag;
     ResourceTable* mTable;
     Source mSource;
     ConfigDescription mConfig;
+    ResourceParserOptions mOptions;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index cb98afd..a7e9d39 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -48,10 +48,12 @@
         mContext = test::ContextBuilder().build();
     }
 
-    ::testing::AssertionResult testParse(const StringPiece& str) {
+    ::testing::AssertionResult testParse(const StringPiece& str,
+                                         Maybe<std::u16string> product = {}) {
         std::stringstream input(kXmlPreamble);
         input << "<resources>\n" << str << "\n</resources>" << std::endl;
-        ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {});
+        ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {},
+                              ResourceParserOptions{ product });
         XmlPullParser xmlParser(input);
         if (parser.parse(&xmlParser)) {
             return ::testing::AssertionSuccess();
@@ -314,6 +316,9 @@
     std::string input = "<declare-styleable name=\"foo\">\n"
                         "  <attr name=\"bar\" />\n"
                         "  <attr name=\"bat\" format=\"string|reference\"/>\n"
+                        "  <attr name=\"baz\">\n"
+                        "    <enum name=\"foo\" value=\"1\"/>\n"
+                        "  </attr>\n"
                         "</declare-styleable>";
     ASSERT_TRUE(testParse(input));
 
@@ -325,9 +330,16 @@
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
 
+    attr = test::getValue<Attribute>(&mTable, u"@attr/baz");
+    ASSERT_NE(attr, nullptr);
+    EXPECT_TRUE(attr->isWeak());
+    EXPECT_EQ(1u, attr->symbols.size());
+
+    EXPECT_NE(nullptr, test::getValue<Id>(&mTable, u"@id/foo"));
+
     Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
     ASSERT_NE(styleable, nullptr);
-    ASSERT_EQ(2u, styleable->entries.size());
+    ASSERT_EQ(3u, styleable->entries.size());
 
     EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value());
     EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
@@ -350,6 +362,14 @@
     EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
 }
 
+TEST_F(ResourceParserTest, ParseStringArray) {
+    std::string input = "<string-array name=\"foo\">\n"
+                        "  <item>\"Werk\"</item>\n"
+                        "</string-array>\n";
+    ASSERT_TRUE(testParse(input));
+    EXPECT_NE(nullptr, test::getValue<Array>(&mTable, u"@array/foo"));
+}
+
 TEST_F(ResourceParserTest, ParsePlural) {
     std::string input = "<plurals name=\"foo\">\n"
                         "  <item quantity=\"other\">apples</item>\n"
@@ -385,4 +405,24 @@
     ASSERT_NE(nullptr, id);
 }
 
+TEST_F(ResourceParserTest, FilterProductsThatDontMatch) {
+    std::string input = "<string name=\"foo\" product=\"phone\">hi</string>\n"
+                        "<string name=\"foo\" product=\"no-sdcard\">ho</string>\n"
+                        "<string name=\"bar\" product=\"\">wee</string>\n"
+                        "<string name=\"baz\">woo</string>\n";
+    ASSERT_TRUE(testParse(input, std::u16string(u"no-sdcard")));
+
+    String* fooStr = test::getValue<String>(&mTable, u"@string/foo");
+    ASSERT_NE(nullptr, fooStr);
+    EXPECT_EQ(StringPiece16(u"ho"), *fooStr->value);
+
+    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bar"));
+    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/baz"));
+}
+
+TEST_F(ResourceParserTest, FailWhenProductFilterStripsOutAllVersionsOfResource) {
+    std::string input = "<string name=\"foo\" product=\"tablet\">hello</string>\n";
+    ASSERT_FALSE(testParse(input, std::u16string(u"phone")));
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index a1e7d36..e32fb5e 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -62,17 +62,17 @@
     return nullptr;
 }
 
-ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, uint8_t id) {
+ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, Maybe<uint8_t> id) {
     ResourceTablePackage* package = findOrCreatePackage(name);
-    if (!package->id) {
+    if (id && !package->id) {
         package->id = id;
         return package;
     }
 
-    if (package->id.value() == id) {
-        return package;
+    if (id && package->id && package->id.value() != id.value()) {
+        return nullptr;
     }
-    return nullptr;
+    return package;
 }
 
 ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece16& name) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index a00c142..60fed2f 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -176,9 +176,9 @@
     bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
                                 const Source& source, IDiagnostics* diag);
     struct SearchResult {
-	ResourceTablePackage* package;
-	ResourceTableType* type;
-	ResourceEntry* entry;
+        ResourceTablePackage* package;
+        ResourceTableType* type;
+        ResourceEntry* entry;
     };
 
     Maybe<SearchResult> findResource(const ResourceNameRef& name);
@@ -208,7 +208,7 @@
 
     ResourceTablePackage* findPackageById(uint8_t id);
 
-    ResourceTablePackage* createPackage(const StringPiece16& name, uint8_t id);
+    ResourceTablePackage* createPackage(const StringPiece16& name, Maybe<uint8_t> id = {});
 
 private:
     ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 498bc9c..0bc5dce 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -102,6 +102,7 @@
 
 struct CompileOptions {
     std::string outputPath;
+    Maybe<std::u16string> product;
     bool verbose = false;
 };
 
@@ -121,8 +122,6 @@
 static bool compileTable(IAaptContext* context, const CompileOptions& options,
                          const ResourcePathData& pathData, const std::string& outputPath) {
     ResourceTable table;
-    table.createPackage(u"", 0x7f);
-
     {
         std::ifstream fin(pathData.source.path, std::ifstream::binary);
         if (!fin) {
@@ -134,7 +133,7 @@
         // Parse the values file from XML.
         XmlPullParser xmlParser(fin);
         ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
-                                 pathData.config);
+                                 pathData.config, ResourceParserOptions{ options.product });
         if (!resParser.parse(&xmlParser)) {
             return false;
         }
@@ -142,6 +141,12 @@
         fin.close();
     }
 
+    ResourceTablePackage* pkg = table.createPackage(context->getCompilationPackage());
+    if (!pkg->id) {
+        // If no package ID was set while parsing (public identifiers), auto assign an ID.
+        pkg->id = context->getPackageId();
+    }
+
     // Assign IDs to prepare the table for flattening.
     IdAssigner idAssigner;
     if (!idAssigner.consume(context, &table)) {
@@ -325,7 +330,7 @@
     }
 
     uint8_t getPackageId() override {
-       return 0x7f;
+       return 0x0;
     }
 
     ISymbolTable* getExternalSymbols() override {
@@ -340,13 +345,19 @@
 int compile(const std::vector<StringPiece>& args) {
     CompileOptions options;
 
+    Maybe<std::string> product;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
+            .optionalFlag("--product", "Product type to compile", &product)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
     if (!flags.parse("aapt2 compile", args, &std::cerr)) {
         return 1;
     }
 
+    if (product) {
+        options.product = util::utf8ToUtf16(product.value());
+    }
+
     CompileContext context;
 
     std::vector<ResourcePathData> inputData;
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 427ab18..75cbd5d 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -23,9 +23,9 @@
 #include "flatten/TableFlattener.h"
 #include "util/BigBuffer.h"
 
+#include <base/macros.h>
 #include <type_traits>
 #include <numeric>
-#include <utils/misc.h>
 
 using namespace android;
 
@@ -59,20 +59,32 @@
     uint32_t sourceLine;
 };
 
-struct SymbolWriter {
+class SymbolWriter {
+public:
     struct Entry {
         StringPool::Ref name;
         size_t offset;
     };
 
-    StringPool pool;
     std::vector<Entry> symbols;
 
-    void addSymbol(const ResourceNameRef& name, size_t offset) {
-        symbols.push_back(Entry{ pool.makeRef(name.package.toString() + u":" +
-                                              toString(name.type).toString() + u"/" +
-                                              name.entry.toString()), offset });
+    explicit SymbolWriter(StringPool* pool) : mPool(pool) {
     }
+
+    void addSymbol(const ResourceNameRef& name, size_t offset) {
+        symbols.push_back(Entry{ mPool->makeRef(name.package.toString() + u":" +
+                                               toString(name.type).toString() + u"/" +
+                                               name.entry.toString()), offset });
+    }
+
+    void shiftAllOffsets(size_t offset) {
+        for (Entry& entry : symbols) {
+            entry.offset += offset;
+        }
+    }
+
+private:
+    StringPool* mPool;
 };
 
 struct MapFlattenVisitor : public RawValueVisitor {
@@ -226,15 +238,59 @@
     }
 };
 
-struct PackageFlattener {
+class PackageFlattener {
+public:
+    PackageFlattener(IDiagnostics* diag, TableFlattenerOptions options,
+                     ResourceTablePackage* package, SymbolWriter* symbolWriter,
+                     StringPool* sourcePool) :
+            mDiag(diag), mOptions(options), mPackage(package), mSymbols(symbolWriter),
+            mSourcePool(sourcePool) {
+    }
+
+    bool flattenPackage(BigBuffer* buffer) {
+        ChunkWriter pkgWriter(buffer);
+        ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
+                RES_TABLE_PACKAGE_TYPE);
+        pkgHeader->id = util::hostToDevice32(mPackage->id.value());
+
+        if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
+            mDiag->error(DiagMessage() <<
+                         "package name '" << mPackage->name << "' is too long");
+            return false;
+        }
+
+        // Copy the package name in device endianness.
+        strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), mPackage->name);
+
+        // Serialize the types. We do this now so that our type and key strings
+        // are populated. We write those first.
+        BigBuffer typeBuffer(1024);
+        flattenTypes(&typeBuffer);
+
+        pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+
+        pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
+
+        // Add the ResTable_package header/type/key strings to the offset.
+        mSymbols->shiftAllOffsets(pkgWriter.size());
+
+        // Append the types.
+        buffer->appendBuffer(std::move(typeBuffer));
+
+        pkgWriter.finish();
+        return true;
+    }
+
+private:
     IDiagnostics* mDiag;
     TableFlattenerOptions mOptions;
-    ResourceTable* mTable;
     ResourceTablePackage* mPackage;
-    SymbolWriter mSymbols;
     StringPool mTypePool;
     StringPool mKeyPool;
-    StringPool mSourcePool;
+    SymbolWriter* mSymbols;
+    StringPool* mSourcePool;
 
     template <typename T>
     T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
@@ -278,8 +334,8 @@
             if (Reference* ref = valueCast<Reference>(entry->value)) {
                 if (!ref->id) {
                     assert(ref->name && "reference must have at least a name");
-                    mSymbols.addSymbol(ref->name.value(),
-                                       buffer->size() + offsetof(Res_value, data));
+                    mSymbols->addSymbol(ref->name.value(),
+                                        buffer->size() + offsetof(Res_value, data));
                 }
             }
             Res_value* outValue = buffer->nextBlock<Res_value>();
@@ -289,12 +345,12 @@
         } else {
             const size_t beforeEntry = buffer->size();
             ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
-            MapFlattenVisitor visitor(&mSymbols, entry, buffer);
+            MapFlattenVisitor visitor(mSymbols, entry, buffer);
             entry->value->accept(&visitor);
             outEntry->count = util::hostToDevice32(visitor.mEntryCount);
             if (visitor.mParentName) {
-                mSymbols.addSymbol(visitor.mParentName.value(),
-                                   beforeEntry + offsetof(ResTable_entry_ext, parent));
+                mSymbols->addSymbol(visitor.mParentName.value(),
+                                    beforeEntry + offsetof(ResTable_entry_ext, parent));
             } else if (visitor.mParentIdent) {
                 outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value());
             }
@@ -430,7 +486,7 @@
                 publicEntry->entryId = util::hostToDevice32(entry->id.value());
                 publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
                         entry->name).getIndex());
-                publicEntry->source.index = util::hostToDevice32(mSourcePool.makeRef(
+                publicEntry->source.index = util::hostToDevice32(mSourcePool->makeRef(
                         util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
                 if (entry->publicStatus.source.line) {
                     publicEntry->sourceLine = util::hostToDevice32(
@@ -487,7 +543,7 @@
                 for (auto& configValue : entry->values) {
                    configToEntryListMap[configValue.config].push_back(FlatEntry{
                             entry, configValue.value.get(), (uint32_t) keyIndex,
-                            (uint32_t)(mSourcePool.makeRef(util::utf8ToUtf16(
+                            (uint32_t)(mSourcePool->makeRef(util::utf8ToUtf16(
                                     configValue.source.path)).getIndex()),
                             (uint32_t)(configValue.source.line
                                     ? configValue.source.line.value() : 0)
@@ -504,141 +560,113 @@
         }
         return true;
     }
-
-    bool flattenPackage(BigBuffer* buffer) {
-        // We must do this before writing the resources, since the string pool IDs may change.
-        mTable->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
-            int diff = a.context.priority - b.context.priority;
-            if (diff < 0) return true;
-            if (diff > 0) return false;
-            diff = a.context.config.compare(b.context.config);
-            if (diff < 0) return true;
-            if (diff > 0) return false;
-            return a.value < b.value;
-        });
-        mTable->stringPool.prune();
-
-        const size_t beginningIndex = buffer->size();
-
-        BigBuffer typeBuffer(1024);
-        if (!flattenTypes(&typeBuffer)) {
-            return false;
-        }
-
-        ChunkWriter tableWriter(buffer);
-        ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
-        tableHeader->packageCount = util::hostToDevice32(1);
-
-        SymbolTable_entry* symbolEntryData = nullptr;
-        if (mOptions.useExtendedChunks && !mSymbols.symbols.empty()) {
-            // Sort the offsets so we can scan them linearly.
-            std::sort(mSymbols.symbols.begin(), mSymbols.symbols.end(),
-                      [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
-                          return a.offset < b.offset;
-                      });
-
-            ChunkWriter symbolWriter(tableWriter.getBuffer());
-            SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
-                    RES_TABLE_SYMBOL_TABLE_TYPE);
-            symbolHeader->count = util::hostToDevice32(mSymbols.symbols.size());
-
-            symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(mSymbols.symbols.size());
-            StringPool::flattenUtf8(symbolWriter.getBuffer(), mSymbols.pool);
-            symbolWriter.finish();
-        }
-
-        if (mOptions.useExtendedChunks && mSourcePool.size() > 0) {
-            // Write out source pool.
-            ChunkWriter srcWriter(tableWriter.getBuffer());
-            srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
-            StringPool::flattenUtf8(srcWriter.getBuffer(), mSourcePool);
-            srcWriter.finish();
-        }
-
-        StringPool::flattenUtf8(tableWriter.getBuffer(), mTable->stringPool);
-
-        ChunkWriter pkgWriter(tableWriter.getBuffer());
-        ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
-                RES_TABLE_PACKAGE_TYPE);
-        pkgHeader->id = util::hostToDevice32(mPackage->id.value());
-
-        if (mPackage->name.size() >= NELEM(pkgHeader->name)) {
-            mDiag->error(DiagMessage() <<
-                         "package name '" << mPackage->name << "' is too long");
-            return false;
-        }
-
-        strcpy16_htod(pkgHeader->name, NELEM(pkgHeader->name), mPackage->name);
-
-        pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
-        StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
-
-        pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
-        StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
-
-        // Actually write out the symbol entries if we have symbols.
-        if (symbolEntryData) {
-            for (auto& entry : mSymbols.symbols) {
-                symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
-
-                // The symbols were all calculated with the typeBuffer offset. We need to
-                // add the beginning of the output buffer.
-                symbolEntryData->offset = util::hostToDevice32(
-                        (pkgWriter.getBuffer()->size() - beginningIndex) + entry.offset);
-
-                symbolEntryData++;
-            }
-        }
-
-        // Write out the types and entries.
-        pkgWriter.getBuffer()->appendBuffer(std::move(typeBuffer));
-
-        pkgWriter.finish();
-        tableWriter.finish();
-        return true;
-    }
 };
 
 } // namespace
 
 bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
+    // We must do this before writing the resources, since the string pool IDs may change.
+    table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+        int diff = a.context.priority - b.context.priority;
+        if (diff < 0) return true;
+        if (diff > 0) return false;
+        diff = a.context.config.compare(b.context.config);
+        if (diff < 0) return true;
+        if (diff > 0) return false;
+        return a.value < b.value;
+    });
+    table->stringPool.prune();
+
+    // Write the ResTable header.
+    ChunkWriter tableWriter(mBuffer);
+    ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
+    tableHeader->packageCount = util::hostToDevice32(table->packages.size());
+
+    // Flatten the values string pool.
+    StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
+
+    // If we have a reference to a symbol that doesn't exist, we don't know its resource ID.
+    // We encode the name of the symbol along with the offset of where to include the resource ID
+    // once it is found.
+    StringPool symbolPool;
+    std::vector<SymbolWriter::Entry> symbolOffsets;
+
+    // String pool holding the source paths of each value.
+    StringPool sourcePool;
+
+    BigBuffer packageBuffer(1024);
+
+    // Flatten each package.
     for (auto& package : table->packages) {
-        // Only support flattening one package. Since the StringPool is shared between packages
-        // in ResourceTable, we must fail if other packages are present, since their strings
-        // will be included in the final ResourceTable.
-        if (context->getCompilationPackage() != package->name) {
-            context->getDiagnostics()->error(DiagMessage()
-                                             << "resources for package '" << package->name
-                                             << "' can't be flattened when compiling package '"
-                                             << context->getCompilationPackage() << "'");
+        const size_t beforePackageSize = packageBuffer.size();
+
+        // All packages will share a single global symbol pool.
+        SymbolWriter packageSymbolWriter(&symbolPool);
+
+        PackageFlattener flattener(context->getDiagnostics(), mOptions, package.get(),
+                                   &packageSymbolWriter, &sourcePool);
+        if (!flattener.flattenPackage(&packageBuffer)) {
             return false;
         }
 
-        if (!package->id || package->id.value() != context->getPackageId()) {
-            context->getDiagnostics()->error(DiagMessage()
-                                             << "package '" << package->name << "' must have "
-                                             << "package id "
-                                             << std::hex << context->getPackageId() << std::dec);
-            return false;
-        }
+        // The symbols are offset only from their own Package start. Offset them from the
+        // start of the packageBuffer.
+        packageSymbolWriter.shiftAllOffsets(beforePackageSize);
 
-        PackageFlattener flattener = {
-                context->getDiagnostics(),
-                mOptions,
-                table,
-                package.get()
-        };
-
-        if (!flattener.flattenPackage(mBuffer)) {
-            return false;
-        }
-        return true;
+        // Extract all the symbols to offset
+        symbolOffsets.insert(symbolOffsets.end(),
+                             std::make_move_iterator(packageSymbolWriter.symbols.begin()),
+                             std::make_move_iterator(packageSymbolWriter.symbols.end()));
     }
 
-    context->getDiagnostics()->error(DiagMessage()
-                                     << "compilation package '" << context->getCompilationPackage()
-                                     << "' not found");
-    return false;
+    SymbolTable_entry* symbolEntryData = nullptr;
+    if (mOptions.useExtendedChunks) {
+        if (!symbolOffsets.empty()) {
+            // Sort the offsets so we can scan them linearly.
+            std::sort(symbolOffsets.begin(), symbolOffsets.end(),
+                      [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
+                          return a.offset < b.offset;
+                      });
+
+            // Write the Symbol header.
+            ChunkWriter symbolWriter(tableWriter.getBuffer());
+            SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
+                    RES_TABLE_SYMBOL_TABLE_TYPE);
+            symbolHeader->count = util::hostToDevice32(symbolOffsets.size());
+
+            symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(symbolOffsets.size());
+            StringPool::flattenUtf8(symbolWriter.getBuffer(), symbolPool);
+            symbolWriter.finish();
+        }
+
+        if (sourcePool.size() > 0) {
+            // Write out source pool.
+            ChunkWriter srcWriter(tableWriter.getBuffer());
+            srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
+            StringPool::flattenUtf8(srcWriter.getBuffer(), sourcePool);
+            srcWriter.finish();
+        }
+    }
+
+    const size_t beforePackagesSize = tableWriter.size();
+
+    // Finally merge all the packages into the main buffer.
+    tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
+
+    // Update the offsets to their final values.
+    if (symbolEntryData) {
+        for (SymbolWriter::Entry& entry : symbolOffsets) {
+            symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
+
+            // The symbols were all calculated with the packageBuffer offset. We need to
+            // add the beginning of the output buffer.
+            symbolEntryData->offset = util::hostToDevice32(entry.offset + beforePackagesSize);
+            symbolEntryData++;
+        }
+    }
+
+    tableWriter.finish();
+    return true;
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 2b4c4d2..fa321a0 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -52,6 +52,7 @@
     bool staticLib = false;
     bool verbose = false;
     bool outputToDirectory = false;
+    Maybe<std::string> privateSymbols;
 };
 
 struct LinkContext : public IAaptContext {
@@ -400,7 +401,13 @@
 
         mContext.mNameMangler = util::make_unique<NameMangler>(
                 NameManglerPolicy{ mContext.mCompilationPackage });
-        mContext.mPackageId = 0x7f;
+
+        if (mContext.mCompilationPackage == u"android") {
+            mContext.mPackageId = 0x01;
+        } else {
+            mContext.mPackageId = 0x7f;
+        }
+
         mContext.mSymbols = createSymbolTableFromIncludePaths();
         if (!mContext.mSymbols) {
             return 1;
@@ -689,6 +696,9 @@
                             "by -o",
                             &options.outputToDirectory)
             .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
+            .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
+                          "private symbols. If not specified, public and private symbols will "
+                          "use the application's package name", &options.privateSymbols)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index d5fd1fc..0d63b97 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -39,7 +39,7 @@
     bool error = false;
     for (auto& package : table->packages) {
         // Warn of packages with an unrelated ID.
-        if (package->id && package->id.value() != desiredPackageId) {
+        if (package->id && package->id.value() != 0x0 && package->id.value() != desiredPackageId) {
             mContext->getDiagnostics()->warn(DiagMessage(src)
                                              << "ignoring package " << package->name);
             continue;
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index b41c568..6fdaebb 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -18,6 +18,7 @@
 #define AAPT_TEST_COMMON_H
 
 #include "ConfigDescription.h"
+#include "Debug.h"
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ValueVisitor.h"
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 992a45c..ac91865 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -27,7 +27,7 @@
 
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/TypeWrappers.h>
-#include <utils/misc.h>
+#include <base/macros.h>
 
 #include <map>
 #include <string>
@@ -289,7 +289,7 @@
     }
 
     // Extract the package name.
-    size_t len = strnlen16((const char16_t*) packageHeader->name, NELEM(packageHeader->name));
+    size_t len = strnlen16((const char16_t*) packageHeader->name, arraysize(packageHeader->name));
     std::u16string packageName;
     packageName.resize(len);
     for (size_t i = 0; i < len; i++) {
@@ -416,13 +416,11 @@
             return false;
         }
 
-        const ResourceId resId = {
-                package->id.value(), header->typeId, util::deviceToHost16(entry->entryId) };
+        const ResourceId resId(package->id.value(), header->typeId,
+                               util::deviceToHost16(entry->entryId));
 
-        const ResourceName name = {
-                package->name,
-                *parsedType,
-                util::getString(mKeyPool, entry->key.index).toString() };
+        const ResourceName name(package->name, *parsedType,
+                                util::getString(mKeyPool, entry->key.index).toString());
 
         Source source;
         if (mSourcePool.getError() == NO_ERROR) {
@@ -516,13 +514,11 @@
             continue;
         }
 
-        const ResourceName name = {
-                package->name,
-                *parsedType,
-                util::getString(mKeyPool, util::deviceToHost32(entry->key.index)).toString() };
+        const ResourceName name(package->name, *parsedType,
+                                util::getString(mKeyPool,
+                                                util::deviceToHost32(entry->key.index)).toString());
 
-        const ResourceId resId =
-                { package->id.value(), type->id, static_cast<uint16_t>(it.index()) };
+        const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
 
         std::unique_ptr<Value> resourceValue;
         const ResTable_entry_source* sourceBlock = nullptr;
@@ -598,7 +594,9 @@
         StringPiece16 str = util::getString(mValuePool, data);
 
         const ResStringPool_span* spans = mValuePool.styleAt(data);
-        if (spans != nullptr) {
+
+        // Check if the string has a valid style associated with it.
+        if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
             StyleString styleStr = { str.toString() };
             while (spans->name.index != ResStringPool_span::END) {
                 styleStr.spans.push_back(Span{
@@ -662,8 +660,12 @@
     switch (name.type) {
         case ResourceType::kStyle:
             return parseStyle(name, config, map);
+        case ResourceType::kAttrPrivate:
+            // fallthrough
         case ResourceType::kAttr:
             return parseAttr(name, config, map);
+        case ResourceType::kIntegerArray:
+            // fallthrough
         case ResourceType::kArray:
             return parseArray(name, config, map);
         case ResourceType::kStyleable:
@@ -671,6 +673,7 @@
         case ResourceType::kPlurals:
             return parsePlural(name, config, map);
         default:
+            assert(false && "unknown map type");
             break;
     }
     return {};