AAPT2: Fix overlay support

Supports the <add-resource> tag and mimics old AAPT behavior of
not allowing new resources defined unless <add-resource> was used
or --auto-add-overlay was specified.

Change-Id: I9b461137357617ade37fd7045b418b8e6450b9c4
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index b7ffba7..fa4afd3 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
+#include "io/FileSystem.h"
 #include "link/TableMerger.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
 
@@ -57,10 +57,11 @@
             .build();
 
     ResourceTable finalTable;
-    TableMerger merger(mContext.get(), &finalTable);
+    TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+    io::FileCollection collection;
 
-    ASSERT_TRUE(merger.merge({}, tableA.get(), false));
-    ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection));
 
     EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
 
@@ -76,6 +77,60 @@
     AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo")));
 }
 
+TEST_F(TableMergerTest, MergeFile) {
+    ResourceTable finalTable;
+    TableMergerOptions options;
+    options.autoAddOverlay = false;
+    TableMerger merger(mContext.get(), &finalTable, options);
+
+    ResourceFile fileDesc;
+    fileDesc.config = test::parseConfigOrDie("hdpi-v4");
+    fileDesc.name = test::parseNameOrDie(u"@layout/main");
+    fileDesc.source = Source("res/layout-hdpi/main.xml");
+    test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat");
+
+    ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile));
+
+    FileReference* file = test::getValueForConfig<FileReference>(&finalTable,
+                                                                 u"@com.app.a:layout/main",
+                                                                 test::parseConfigOrDie("hdpi-v4"));
+    ASSERT_NE(nullptr, file);
+    EXPECT_EQ(std::u16string(u"res/layout-hdpi-v4/main.xml"), *file->path);
+
+    ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
+    ResourceKeyRef key = { name, test::parseConfigOrDie("hdpi-v4") };
+
+    auto iter = merger.getFilesToMerge().find(key);
+    ASSERT_NE(merger.getFilesToMerge().end(), iter);
+
+    const FileToMerge& actualFileToMerge = iter->second;
+    EXPECT_EQ(&testFile, actualFileToMerge.file);
+    EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), actualFileToMerge.dstPath);
+}
+
+TEST_F(TableMergerTest, MergeFileOverlay) {
+    ResourceTable finalTable;
+    TableMergerOptions tableMergerOptions;
+    tableMergerOptions.autoAddOverlay = false;
+    TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+
+    ResourceFile fileDesc;
+    fileDesc.name = test::parseNameOrDie(u"@xml/foo");
+    test::TestFile fileA("path/to/fileA.xml.flat");
+    test::TestFile fileB("path/to/fileB.xml.flat");
+
+    ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
+    ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
+
+    ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/foo");
+    ResourceKeyRef key = { name, ConfigDescription{} };
+    auto iter = merger.getFilesToMerge().find(key);
+    ASSERT_NE(merger.getFilesToMerge().end(), iter);
+
+    const FileToMerge& actualFileToMerge = iter->second;
+    EXPECT_EQ(&fileB, actualFileToMerge.file);
+}
+
 TEST_F(TableMergerTest, MergeFileReferences) {
     std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
             .setPackageId(u"com.app.a", 0x7f)
@@ -87,10 +142,12 @@
             .build();
 
     ResourceTable finalTable;
-    TableMerger merger(mContext.get(), &finalTable);
+    TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+    io::FileCollection collection;
+    collection.insertFile("res/xml/file.xml");
 
-    ASSERT_TRUE(merger.merge({}, tableA.get(), false));
-    ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection));
 
     FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
     ASSERT_NE(f, nullptr);
@@ -100,13 +157,90 @@
     ASSERT_NE(f, nullptr);
     EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
 
-    std::queue<FileToMerge>* filesToMerge = merger.getFileMergeQueue();
-    ASSERT_FALSE(filesToMerge->empty());
+    ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/com.app.b$file");
+    ResourceKeyRef key = { name, ConfigDescription{} };
+    auto iter = merger.getFilesToMerge().find(key);
+    ASSERT_NE(merger.getFilesToMerge().end(), iter);
 
-    FileToMerge& fileToMerge = filesToMerge->front();
-    EXPECT_EQ(fileToMerge.srcTable, tableB.get());
-    EXPECT_EQ(fileToMerge.srcPath, u"res/xml/file.xml");
-    EXPECT_EQ(fileToMerge.dstPath, u"res/xml/com.app.b$file.xml");
+    const FileToMerge& actualFileToMerge = iter->second;
+    EXPECT_EQ(Source("res/xml/file.xml"), actualFileToMerge.file->getSource());
+    EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), actualFileToMerge.dstPath);
+}
+
+TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
+    std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x00)
+            .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+            .build();
+    std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x00)
+            .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"false"))
+            .build();
+
+    ResourceTable finalTable;
+    TableMergerOptions tableMergerOptions;
+    tableMergerOptions.autoAddOverlay = false;
+    TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
+
+    ASSERT_TRUE(merger.merge({}, base.get()));
+    ASSERT_TRUE(merger.mergeOverlay({}, overlay.get()));
+
+    BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, u"@com.app.a:bool/foo");
+    ASSERT_NE(nullptr, foo);
+    EXPECT_EQ(0x0u, foo->value.data);
+}
+
+TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x7f)
+            .setSymbolState(u"@bool/foo", {}, SymbolState::kUndefined)
+            .build();
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x7f)
+            .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+            .build();
+
+    ResourceTable finalTable;
+    TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{});
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+}
+
+TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x7f)
+            .build();
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x7f)
+            .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+            .build();
+
+    ResourceTable finalTable;
+    TableMergerOptions options;
+    options.autoAddOverlay = true;
+    TableMerger merger(mContext.get(), &finalTable, options);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.mergeOverlay({}, tableB.get()));
+}
+
+TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
+    std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x7f)
+            .build();
+    std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
+            .setPackageId(u"", 0x7f)
+            .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true"))
+            .build();
+
+    ResourceTable finalTable;
+    TableMergerOptions options;
+    options.autoAddOverlay = false;
+    TableMerger merger(mContext.get(), &finalTable, options);
+
+    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
 }
 
 } // namespace aapt