Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 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 "Resolver.h" |
| 18 | #include "ResourceTable.h" |
| 19 | #include "ResourceValues.h" |
| 20 | #include "SourceXmlPullParser.h" |
| 21 | #include "Util.h" |
| 22 | #include "XmlFlattener.h" |
| 23 | |
| 24 | #include <androidfw/AssetManager.h> |
| 25 | #include <androidfw/ResourceTypes.h> |
| 26 | #include <gtest/gtest.h> |
| 27 | #include <sstream> |
| 28 | #include <string> |
| 29 | |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 30 | using namespace android; |
| 31 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 32 | namespace aapt { |
| 33 | |
| 34 | constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; |
| 35 | |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 36 | struct MockResolver : public IResolver { |
| 37 | MockResolver(const StringPiece16& defaultPackage, |
| 38 | const std::map<ResourceName, ResourceId>& items) : |
| 39 | mPackage(defaultPackage.toString()), mAttr(false, ResTable_map::TYPE_ANY), |
| 40 | mItems(items) { |
| 41 | } |
| 42 | |
| 43 | virtual const std::u16string& getDefaultPackage() const override { |
| 44 | return mPackage; |
| 45 | } |
| 46 | |
| 47 | virtual Maybe<ResourceId> findId(const ResourceName& name) override { |
| 48 | const auto iter = mItems.find(name); |
| 49 | if (iter != mItems.end()) { |
| 50 | return iter->second; |
| 51 | } |
| 52 | return {}; |
| 53 | } |
| 54 | |
| 55 | virtual Maybe<Entry> findAttribute(const ResourceName& name) override { |
| 56 | Maybe<ResourceId> result = findId(name); |
| 57 | if (result) { |
| 58 | if (name.type == ResourceType::kAttr) { |
| 59 | return Entry{ result.value(), &mAttr }; |
| 60 | } else { |
| 61 | return Entry{ result.value() }; |
| 62 | } |
| 63 | } |
| 64 | return {}; |
| 65 | } |
| 66 | |
| 67 | virtual Maybe<ResourceName> findName(ResourceId resId) override { |
| 68 | for (auto& p : mItems) { |
| 69 | if (p.second == resId) { |
| 70 | return p.first; |
| 71 | } |
| 72 | } |
| 73 | return {}; |
| 74 | } |
| 75 | |
| 76 | std::u16string mPackage; |
| 77 | Attribute mAttr; |
| 78 | std::map<ResourceName, ResourceId> mItems; |
| 79 | }; |
| 80 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 81 | class XmlFlattenerTest : public ::testing::Test { |
| 82 | public: |
| 83 | virtual void SetUp() override { |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 84 | std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(u"android", |
| 85 | std::map<ResourceName, ResourceId>({ |
| 86 | { ResourceName{ u"android", ResourceType::kAttr, u"attr" }, |
| 87 | ResourceId{ 0x01010000u } }, |
| 88 | { ResourceName{ u"android", ResourceType::kId, u"id" }, |
| 89 | ResourceId{ 0x01020000u } }, |
| 90 | { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" }, |
| 91 | ResourceId{ 0x01010001u } }, |
| 92 | { ResourceName{ u"com.lib", ResourceType::kId, u"id" }, |
| 93 | ResourceId{ 0x01020001u } }})); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 94 | |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 95 | mFlattener = std::make_shared<XmlFlattener>(nullptr, resolver); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 96 | } |
| 97 | |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 98 | ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 99 | std::stringstream input(kXmlPreamble); |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 100 | input << in << std::endl; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 101 | std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input); |
| 102 | BigBuffer outBuffer(1024); |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 103 | XmlFlattener::Options xmlOptions; |
| 104 | xmlOptions.defaultPackage = u"android"; |
| 105 | if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, xmlOptions)) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 106 | return ::testing::AssertionFailure(); |
| 107 | } |
| 108 | |
| 109 | std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 110 | if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 111 | return ::testing::AssertionFailure(); |
| 112 | } |
| 113 | return ::testing::AssertionSuccess(); |
| 114 | } |
| 115 | |
| 116 | std::shared_ptr<XmlFlattener> mFlattener; |
| 117 | }; |
| 118 | |
| 119 | TEST_F(XmlFlattenerTest, ParseSimpleView) { |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 120 | std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| 121 | " android:attr=\"@id/id\">\n" |
| 122 | "</View>"; |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 123 | ResXMLTree tree; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 124 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 125 | |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 126 | while (tree.next() != ResXMLTree::END_DOCUMENT) { |
| 127 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 128 | } |
| 129 | } |
| 130 | |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame^] | 131 | TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) { |
| 132 | std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n" |
| 133 | " xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n" |
| 134 | " ns1:attr=\"@ns2:id/id\">\n" |
| 135 | "</View>"; |
| 136 | ResXMLTree tree; |
| 137 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 138 | |
| 139 | while (tree.next() != ResXMLTree::END_DOCUMENT) { |
| 140 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | ::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index, |
| 145 | ResourceId nameId, ResourceId valueId) { |
| 146 | if (index >= tree->getAttributeCount()) { |
| 147 | return ::testing::AssertionFailure() << "index " << index << " is out of bounds (" |
| 148 | << tree->getAttributeCount() << ")"; |
| 149 | } |
| 150 | |
| 151 | if (tree->getAttributeNameResID(index) != nameId.id) { |
| 152 | return ::testing::AssertionFailure() |
| 153 | << "attribute at index " << index << " has ID " |
| 154 | << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) } |
| 155 | << ". Expected ID " << nameId; |
| 156 | } |
| 157 | |
| 158 | if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) { |
| 159 | return ::testing::AssertionFailure() << "attribute at index " << index << " has value of " |
| 160 | << "type " << std::hex |
| 161 | << tree->getAttributeDataType(index) << std::dec |
| 162 | << ". Expected reference (" << std::hex |
| 163 | << Res_value::TYPE_REFERENCE << std::dec << ")"; |
| 164 | } |
| 165 | |
| 166 | if ((uint32_t) tree->getAttributeData(index) != valueId.id) { |
| 167 | return ::testing::AssertionFailure() |
| 168 | << "attribute at index " << index << " has value " << "with ID " |
| 169 | << ResourceId{ (uint32_t) tree->getAttributeData(index) } |
| 170 | << ". Expected ID " << valueId; |
| 171 | } |
| 172 | return ::testing::AssertionSuccess(); |
| 173 | } |
| 174 | |
| 175 | TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) { |
| 176 | std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" |
| 177 | " app:attr=\"@app:id/id\">\n" |
| 178 | " <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n" |
| 179 | " app:attr=\"@app:id/id\"/>\n" |
| 180 | "</View>"; |
| 181 | ResXMLTree tree; |
| 182 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 183 | |
| 184 | while (tree.next() != ResXMLTree::START_TAG) { |
| 185 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 186 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 187 | } |
| 188 | |
| 189 | ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u }, |
| 190 | ResourceId{ 0x01020000u })); |
| 191 | |
| 192 | while (tree.next() != ResXMLTree::START_TAG) { |
| 193 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 194 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 195 | } |
| 196 | |
| 197 | ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, |
| 198 | ResourceId{ 0x01020001u })); |
| 199 | } |
| 200 | |
| 201 | TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) { |
| 202 | std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n" |
| 203 | " android:attr=\"@id/id\"/>"; |
| 204 | ResXMLTree tree; |
| 205 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 206 | |
| 207 | while (tree.next() != ResXMLTree::START_TAG) { |
| 208 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 209 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 210 | } |
| 211 | |
| 212 | // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace |
| 213 | // assignment. |
| 214 | // However, we didn't give '@id/id' a package, so it should use the default package |
| 215 | // 'android', and not be converted from 'android' to 'com.lib'. |
| 216 | ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, |
| 217 | ResourceId{ 0x01020000u })); |
| 218 | } |
| 219 | |
| 220 | /* |
| 221 | * The device ResXMLParser in libandroidfw differentiates between empty namespace and null |
| 222 | * namespace. |
| 223 | */ |
| 224 | TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { |
| 225 | std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| 226 | " package=\"android\"/>"; |
| 227 | |
| 228 | ResXMLTree tree; |
| 229 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 230 | |
| 231 | while (tree.next() != ResXMLTree::START_TAG) { |
| 232 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 233 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 234 | } |
| 235 | |
| 236 | const StringPiece16 kPackage = u"package"; |
| 237 | EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); |
| 238 | } |
| 239 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 240 | } // namespace aapt |