Add namespace handling in attribute values

Previously, you could only reference namespace prefixes in attribute names:

<View xmlns:appcompat="http://schemas.android.com/apk/res/android.support.v7.appcompat"
      appcompat:name="hey"
      ...

Now you can also reference them in resource names within an attribute value:

      ...
      android:text="@appcompat:string/confirm"
      ...

Which will be treated as "@android.support.v7.appcompat:string/confirm".

Change-Id: Ib076e867a990c80cf877a704eb77cd1ef0b23b52
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
index a7d7ac6..d2139d0 100644
--- a/tools/aapt2/XmlFlattener_test.cpp
+++ b/tools/aapt2/XmlFlattener_test.cpp
@@ -33,30 +33,76 @@
 
 constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
 
+struct MockResolver : public IResolver {
+    MockResolver(const StringPiece16& defaultPackage,
+                 const std::map<ResourceName, ResourceId>& items) :
+            mPackage(defaultPackage.toString()), mAttr(false, ResTable_map::TYPE_ANY),
+            mItems(items) {
+    }
+
+    virtual const std::u16string& getDefaultPackage() const override {
+        return mPackage;
+    }
+
+    virtual Maybe<ResourceId> findId(const ResourceName& name) override {
+        const auto iter = mItems.find(name);
+        if (iter != mItems.end()) {
+            return iter->second;
+        }
+        return {};
+    }
+
+    virtual Maybe<Entry> findAttribute(const ResourceName& name) override {
+        Maybe<ResourceId> result = findId(name);
+        if (result) {
+            if (name.type == ResourceType::kAttr) {
+                return Entry{ result.value(), &mAttr };
+            } else {
+                return Entry{ result.value() };
+            }
+        }
+        return {};
+    }
+
+    virtual Maybe<ResourceName> findName(ResourceId resId) override {
+        for (auto& p : mItems) {
+            if (p.second == resId) {
+                return p.first;
+            }
+        }
+        return {};
+    }
+
+    std::u16string mPackage;
+    Attribute mAttr;
+    std::map<ResourceName, ResourceId> mItems;
+};
+
 class XmlFlattenerTest : public ::testing::Test {
 public:
     virtual void SetUp() override {
-        std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
-        table->setPackage(u"android");
-        table->setPackageId(0x01);
+        std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(u"android",
+                std::map<ResourceName, ResourceId>({
+                        { ResourceName{ u"android", ResourceType::kAttr, u"attr" },
+                          ResourceId{ 0x01010000u } },
+                        { ResourceName{ u"android", ResourceType::kId, u"id" },
+                          ResourceId{ 0x01020000u } },
+                        { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" },
+                          ResourceId{ 0x01010001u } },
+                        { ResourceName{ u"com.lib", ResourceType::kId, u"id" },
+                          ResourceId{ 0x01020001u } }}));
 
-        table->addResource(ResourceName{ {}, ResourceType::kAttr, u"id" },
-                           ResourceId{ 0x01010000 }, {}, {},
-                           util::make_unique<Attribute>(false, ResTable_map::TYPE_ANY));
-
-        table->addResource(ResourceName{ {}, ResourceType::kId, u"test" },
-                           ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>());
-
-        mFlattener = std::make_shared<XmlFlattener>(nullptr,
-                std::make_shared<Resolver>(table, std::make_shared<AssetManager>()));
+        mFlattener = std::make_shared<XmlFlattener>(nullptr, resolver);
     }
 
-    ::testing::AssertionResult testFlatten(std::istream& in, ResXMLTree* outTree) {
+    ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) {
         std::stringstream input(kXmlPreamble);
-        input << in.rdbuf() << std::endl;
+        input << in << std::endl;
         std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
         BigBuffer outBuffer(1024);
-        if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, {})) {
+        XmlFlattener::Options xmlOptions;
+        xmlOptions.defaultPackage = u"android";
+        if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, xmlOptions)) {
             return ::testing::AssertionFailure();
         }
 
@@ -71,11 +117,9 @@
 };
 
 TEST_F(XmlFlattenerTest, ParseSimpleView) {
-    std::stringstream input;
-    input << "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"" << std::endl
-          << "      android:id=\"@id/test\">" << std::endl
-          << "</View>" << std::endl;
-
+    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        "      android:attr=\"@id/id\">\n"
+                        "</View>";
     ResXMLTree tree;
     ASSERT_TRUE(testFlatten(input, &tree));
 
@@ -84,4 +128,113 @@
     }
 }
 
+TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) {
+    std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n"
+                        "      xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n"
+                        "      ns1:attr=\"@ns2:id/id\">\n"
+                        "</View>";
+    ResXMLTree tree;
+    ASSERT_TRUE(testFlatten(input, &tree));
+
+    while (tree.next() != ResXMLTree::END_DOCUMENT) {
+        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+    }
+}
+
+::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index,
+                                                       ResourceId nameId, ResourceId valueId) {
+    if (index >= tree->getAttributeCount()) {
+        return ::testing::AssertionFailure() << "index " << index << " is out of bounds ("
+                                             << tree->getAttributeCount() << ")";
+    }
+
+    if (tree->getAttributeNameResID(index) != nameId.id) {
+        return ::testing::AssertionFailure()
+                << "attribute at index " << index << " has ID "
+                << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) }
+                << ". Expected ID " << nameId;
+    }
+
+    if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) {
+        return ::testing::AssertionFailure() << "attribute at index " << index << " has value of "
+                                             << "type " << std::hex
+                                             << tree->getAttributeDataType(index) << std::dec
+                                             << ". Expected reference (" << std::hex
+                                             << Res_value::TYPE_REFERENCE << std::dec << ")";
+    }
+
+    if ((uint32_t) tree->getAttributeData(index) != valueId.id) {
+        return ::testing::AssertionFailure()
+                << "attribute at index " << index << " has value " << "with ID "
+                << ResourceId{ (uint32_t) tree->getAttributeData(index) }
+                << ". Expected ID " << valueId;
+    }
+    return ::testing::AssertionSuccess();
+}
+
+TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) {
+    std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
+                        "      app:attr=\"@app:id/id\">\n"
+                        "  <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n"
+                        "        app:attr=\"@app:id/id\"/>\n"
+                        "</View>";
+    ResXMLTree tree;
+    ASSERT_TRUE(testFlatten(input, &tree));
+
+    while (tree.next() != ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+    }
+
+    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u },
+                                            ResourceId{ 0x01020000u }));
+
+    while (tree.next() != ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+    }
+
+    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
+                                            ResourceId{ 0x01020001u }));
+}
+
+TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) {
+    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n"
+                        "      android:attr=\"@id/id\"/>";
+    ResXMLTree tree;
+    ASSERT_TRUE(testFlatten(input, &tree));
+
+    while (tree.next() != ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+    }
+
+    // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace
+    // assignment.
+    // However, we didn't give '@id/id' a package, so it should use the default package
+    // 'android', and not be converted from 'android' to 'com.lib'.
+    ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u },
+                                            ResourceId{ 0x01020000u }));
+}
+
+/*
+ * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+ * namespace.
+ */
+TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
+    std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+                        "      package=\"android\"/>";
+
+    ResXMLTree tree;
+    ASSERT_TRUE(testFlatten(input, &tree));
+
+    while (tree.next() != ResXMLTree::START_TAG) {
+        ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT);
+        ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT);
+    }
+
+    const StringPiece16 kPackage = u"package";
+    EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
+}
+
 } // namespace aapt