1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "flatten/XmlFlattener.h"
18#include "link/Linkers.h"
19#include "test/Builders.h"
20#include "test/Context.h"
21#include "util/BigBuffer.h"
22#include "util/Util.h"
23
24#include <androidfw/ResourceTypes.h>
25#include <gtest/gtest.h>
26
27namespace aapt {
28
29class XmlFlattenerTest : public ::testing::Test {
30public:
31    void SetUp() override {
32        mContext = test::ContextBuilder()
33                .setCompilationPackage(u"com.app.test")
34                .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
35                .addSymbolSource(test::StaticSymbolSourceBuilder()
36                        .addSymbol(u"@android:attr/id", ResourceId(0x010100d0),
37                                   test::AttributeBuilder().build())
38                        .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f020000))
39                        .addSymbol(u"@android:attr/paddingStart", ResourceId(0x010103b3),
40                                   test::AttributeBuilder().build())
41                        .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
42                                   test::AttributeBuilder().build())
43                        .build())
44                .build();
45    }
46
47    ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
48                                       XmlFlattenerOptions options = {}) {
49        using namespace android; // For NO_ERROR on windows because it is a macro.
50
51        BigBuffer buffer(1024);
52        XmlFlattener flattener(&buffer, options);
53        if (!flattener.consume(mContext.get(), doc)) {
54            return ::testing::AssertionFailure() << "failed to flatten XML Tree";
55        }
56
57        std::unique_ptr<uint8_t[]> data = util::copy(buffer);
58        if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
59            return ::testing::AssertionFailure() << "flattened XML is corrupt";
60        }
61        return ::testing::AssertionSuccess();
62    }
63
64protected:
65    std::unique_ptr<IAaptContext> mContext;
66};
67
68TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
69    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
70            <View xmlns:test="http://com.test"
71                  attr="hey">
72              <Layout test:hello="hi" />
73              <Layout>Some text</Layout>
74            </View>)EOF");
75
76
77    android::ResXMLTree tree;
78    ASSERT_TRUE(flatten(doc.get(), &tree));
79
80    ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
81
82    size_t len;
83    const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
84    EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
85
86    const char16_t* namespaceUri = tree.getNamespaceUri(&len);
87    ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
88
89    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
90
91    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
92    const char16_t* tagName = tree.getElementName(&len);
93    EXPECT_EQ(StringPiece16(tagName, len), u"View");
94
95    ASSERT_EQ(1u, tree.getAttributeCount());
96    ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
97    const char16_t* attrName = tree.getAttributeName(0, &len);
98    EXPECT_EQ(StringPiece16(attrName, len), u"attr");
99
100    EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
101
102    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
103
104    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
105    tagName = tree.getElementName(&len);
106    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
107
108    ASSERT_EQ(1u, tree.getAttributeCount());
109    const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
110    EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
111
112    attrName = tree.getAttributeName(0, &len);
113    EXPECT_EQ(StringPiece16(attrName, len), u"hello");
114
115    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
116    ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
117
118    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
119    tagName = tree.getElementName(&len);
120    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
121    ASSERT_EQ(0u, tree.getAttributeCount());
122
123    ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
124    const char16_t* text = tree.getText(&len);
125    EXPECT_EQ(StringPiece16(text, len), u"Some text");
126
127    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
128    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
129    tagName = tree.getElementName(&len);
130    EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
131
132    ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
133    ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
134    tagName = tree.getElementName(&len);
135    EXPECT_EQ(StringPiece16(tagName, len), u"View");
136
137    ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
138    namespacePrefix = tree.getNamespacePrefix(&len);
139    EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
140
141    namespaceUri = tree.getNamespaceUri(&len);
142    ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
143
144    ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
145}
146
147TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
148    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
149            <View xmlns:android="http://schemas.android.com/apk/res/android"
150                android:paddingStart="1dp"
151                android:colorAccent="#ffffff"/>)EOF");
152
153    XmlReferenceLinker linker;
154    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
155    ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
156    ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
157
158    android::ResXMLTree tree;
159    XmlFlattenerOptions options;
160    options.maxSdkLevel = 17;
161    ASSERT_TRUE(flatten(doc.get(), &tree, options));
162
163    while (tree.next() != android::ResXMLTree::START_TAG) {
164        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
165        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
166    }
167
168    ASSERT_EQ(1u, tree.getAttributeCount());
169    EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
170}
171
172TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
173    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
174            <View xmlns:android="http://schemas.android.com/apk/res/android"
175                  android:id="@id/id"
176                  class="str"
177                  style="@id/id"/>)EOF");
178
179    android::ResXMLTree tree;
180    ASSERT_TRUE(flatten(doc.get(), &tree));
181
182    while (tree.next() != android::ResXMLTree::START_TAG) {
183        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
184        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
185    }
186
187    EXPECT_EQ(tree.indexOfClass(), 0);
188    EXPECT_EQ(tree.indexOfStyle(), 1);
189}
190
191/*
192 * The device ResXMLParser in libandroidfw differentiates between empty namespace and null
193 * namespace.
194 */
195TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
196    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
197
198    android::ResXMLTree tree;
199    ASSERT_TRUE(flatten(doc.get(), &tree));
200
201    while (tree.next() != android::ResXMLTree::START_TAG) {
202        ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
203        ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
204    }
205
206    const StringPiece16 kPackage = u"package";
207    EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
208}
209
210} // namespace aapt
211