1/*
2 * Copyright (C) 2017 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 "link/XmlCompatVersioner.h"
18
19#include "Linkers.h"
20#include "test/Test.h"
21
22namespace aapt {
23
24constexpr auto TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION;
25constexpr auto TYPE_STRING = android::ResTable_map::TYPE_STRING;
26
27struct R {
28  struct attr {
29    enum : uint32_t {
30      paddingLeft = 0x010100d6u,         // (API 1)
31      paddingRight = 0x010100d8u,        // (API 1)
32      progressBarPadding = 0x01010319u,  // (API 11)
33      paddingStart = 0x010103b3u,        // (API 17)
34      paddingHorizontal = 0x0101053du,   // (API 26)
35    };
36  };
37};
38
39class XmlCompatVersionerTest : public ::testing::Test {
40 public:
41  void SetUp() override {
42    context_ =
43        test::ContextBuilder()
44            .SetCompilationPackage("com.app")
45            .SetPackageId(0x7f)
46            .SetPackageType(PackageType::kApp)
47            .SetMinSdkVersion(SDK_GINGERBREAD)
48            .AddSymbolSource(
49                test::StaticSymbolSourceBuilder()
50                    .AddPublicSymbol("android:attr/paddingLeft", R::attr::paddingLeft,
51                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
52                    .AddPublicSymbol("android:attr/paddingRight", R::attr::paddingRight,
53                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
54                    .AddPublicSymbol("android:attr/progressBarPadding", R::attr::progressBarPadding,
55                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
56                    .AddPublicSymbol("android:attr/paddingStart", R::attr::paddingStart,
57                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
58                    .AddPublicSymbol("android:attr/paddingHorizontal", R::attr::paddingHorizontal,
59                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
60                    .AddSymbol("com.app:attr/foo", ResourceId(0x7f010000),
61                               util::make_unique<Attribute>(false, TYPE_STRING))
62                    .Build())
63            .Build();
64  }
65
66 protected:
67  std::unique_ptr<IAaptContext> context_;
68};
69
70TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) {
71  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
72      <View xmlns:android="http://schemas.android.com/apk/res/android"
73          xmlns:app="http://schemas.android.com/apk/res-auto"
74          android:paddingHorizontal="24dp"
75          app:foo="16dp"
76          foo="bar"/>)EOF");
77
78  XmlReferenceLinker linker;
79  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
80
81  XmlCompatVersioner::Rules rules;
82  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
83
84  XmlCompatVersioner versioner(&rules);
85  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
86      versioner.Process(context_.get(), doc.get(), api_range);
87  ASSERT_EQ(2u, versioned_docs.size());
88
89  xml::Element* el;
90
91  // Source XML file's sdkVersion == 0, so the first one must also have the same sdkVersion.
92  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
93  el = xml::FindRootElement(versioned_docs[0].get());
94  ASSERT_NE(nullptr, el);
95  EXPECT_EQ(2u, el->attributes.size());
96  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
97  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
98  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
99
100  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion);
101  el = xml::FindRootElement(versioned_docs[1].get());
102  ASSERT_NE(nullptr, el);
103  EXPECT_EQ(3u, el->attributes.size());
104  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
105  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
106  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
107}
108
109TEST_F(XmlCompatVersionerTest, SingleRule) {
110  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
111      <View xmlns:android="http://schemas.android.com/apk/res/android"
112          xmlns:app="http://schemas.android.com/apk/res-auto"
113          android:paddingHorizontal="24dp"
114          app:foo="16dp"
115          foo="bar"/>)EOF");
116
117  XmlReferenceLinker linker;
118  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
119
120  XmlCompatVersioner::Rules rules;
121  rules[R::attr::paddingHorizontal] =
122      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
123          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
124           ReplacementAttr{"paddingRight", R::attr::paddingRight,
125                           Attribute(false, TYPE_DIMENSION)}}));
126
127  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
128
129  XmlCompatVersioner versioner(&rules);
130  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
131      versioner.Process(context_.get(), doc.get(), api_range);
132  ASSERT_EQ(2u, versioned_docs.size());
133
134  xml::Element* el;
135
136  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
137  el = xml::FindRootElement(versioned_docs[0].get());
138  ASSERT_NE(nullptr, el);
139  EXPECT_EQ(4u, el->attributes.size());
140  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
141  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
142  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
143
144  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
145  ASSERT_NE(nullptr, attr);
146  ASSERT_NE(nullptr, attr->compiled_value);
147  ASSERT_TRUE(attr->compiled_attribute);
148
149  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
150  ASSERT_NE(nullptr, attr);
151  ASSERT_NE(nullptr, attr->compiled_value);
152  ASSERT_TRUE(attr->compiled_attribute);
153
154  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion);
155  el = xml::FindRootElement(versioned_docs[1].get());
156  ASSERT_NE(nullptr, el);
157  EXPECT_EQ(5u, el->attributes.size());
158  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
159  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
160  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
161
162  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
163  ASSERT_NE(nullptr, attr);
164  ASSERT_NE(nullptr, attr->compiled_value);
165  ASSERT_TRUE(attr->compiled_attribute);
166
167  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
168  ASSERT_NE(nullptr, attr);
169  ASSERT_NE(nullptr, attr->compiled_value);
170  ASSERT_TRUE(attr->compiled_attribute);
171}
172
173TEST_F(XmlCompatVersionerTest, ChainedRule) {
174  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
175      <View xmlns:android="http://schemas.android.com/apk/res/android"
176          android:paddingHorizontal="24dp" />)EOF");
177
178  XmlReferenceLinker linker;
179  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
180
181  XmlCompatVersioner::Rules rules;
182  rules[R::attr::progressBarPadding] =
183      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
184          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
185           ReplacementAttr{"paddingRight", R::attr::paddingRight,
186                           Attribute(false, TYPE_DIMENSION)}}));
187  rules[R::attr::paddingHorizontal] =
188      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>({ReplacementAttr{
189          "progressBarPadding", R::attr::progressBarPadding, Attribute(false, TYPE_DIMENSION)}}));
190
191  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
192
193  XmlCompatVersioner versioner(&rules);
194  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
195      versioner.Process(context_.get(), doc.get(), api_range);
196  ASSERT_EQ(3u, versioned_docs.size());
197
198  xml::Element* el;
199
200  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
201  el = xml::FindRootElement(versioned_docs[0].get());
202  ASSERT_NE(nullptr, el);
203  EXPECT_EQ(2u, el->attributes.size());
204  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
205
206  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
207  ASSERT_NE(nullptr, attr);
208  ASSERT_NE(nullptr, attr->compiled_value);
209  ASSERT_TRUE(attr->compiled_attribute);
210
211  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
212  ASSERT_NE(nullptr, attr);
213  ASSERT_NE(nullptr, attr->compiled_value);
214  ASSERT_TRUE(attr->compiled_attribute);
215
216  EXPECT_EQ(static_cast<uint16_t>(SDK_HONEYCOMB), versioned_docs[1]->file.config.sdkVersion);
217  el = xml::FindRootElement(versioned_docs[1].get());
218  ASSERT_NE(nullptr, el);
219  EXPECT_EQ(1u, el->attributes.size());
220  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
221  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"));
222  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight"));
223
224  attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding");
225  ASSERT_NE(nullptr, attr);
226  ASSERT_NE(nullptr, attr->compiled_value);
227  ASSERT_TRUE(attr->compiled_attribute);
228
229  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[2]->file.config.sdkVersion);
230  el = xml::FindRootElement(versioned_docs[2].get());
231  ASSERT_NE(nullptr, el);
232  EXPECT_EQ(2u, el->attributes.size());
233  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"));
234  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight"));
235
236  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal");
237  ASSERT_NE(nullptr, attr);
238  ASSERT_NE(nullptr, attr->compiled_value);
239  ASSERT_TRUE(attr->compiled_attribute);
240
241  attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding");
242  ASSERT_NE(nullptr, attr);
243  ASSERT_NE(nullptr, attr->compiled_value);
244  ASSERT_TRUE(attr->compiled_attribute);
245}
246
247TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) {
248  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
249      <View xmlns:android="http://schemas.android.com/apk/res/android"
250          android:paddingHorizontal="24dp"
251          android:paddingLeft="16dp"
252          android:paddingRight="16dp"/>)EOF");
253
254  XmlReferenceLinker linker;
255  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
256
257  Item* padding_horizontal_value = xml::FindRootElement(doc.get())
258                                       ->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")
259                                       ->compiled_value.get();
260  ASSERT_NE(nullptr, padding_horizontal_value);
261
262  XmlCompatVersioner::Rules rules;
263  rules[R::attr::paddingHorizontal] =
264      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
265          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
266           ReplacementAttr{"paddingRight", R::attr::paddingRight,
267                           Attribute(false, TYPE_DIMENSION)}}));
268
269  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
270
271  XmlCompatVersioner versioner(&rules);
272  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
273      versioner.Process(context_.get(), doc.get(), api_range);
274  ASSERT_EQ(2u, versioned_docs.size());
275
276  xml::Element* el;
277
278  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
279  el = xml::FindRootElement(versioned_docs[0].get());
280  ASSERT_NE(nullptr, el);
281  EXPECT_EQ(2u, el->attributes.size());
282  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
283
284  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
285  ASSERT_NE(nullptr, attr);
286  ASSERT_NE(nullptr, attr->compiled_value);
287  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
288  ASSERT_TRUE(attr->compiled_attribute);
289
290  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
291  ASSERT_NE(nullptr, attr);
292  ASSERT_NE(nullptr, attr->compiled_value);
293  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
294  ASSERT_TRUE(attr->compiled_attribute);
295
296  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion);
297  el = xml::FindRootElement(versioned_docs[1].get());
298  ASSERT_NE(nullptr, el);
299  EXPECT_EQ(3u, el->attributes.size());
300
301  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal");
302  ASSERT_NE(nullptr, attr);
303  ASSERT_NE(nullptr, attr->compiled_value);
304  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
305  ASSERT_TRUE(attr->compiled_attribute);
306
307  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
308  ASSERT_NE(nullptr, attr);
309  ASSERT_NE(nullptr, attr->compiled_value);
310  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
311  ASSERT_TRUE(attr->compiled_attribute);
312
313  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
314  ASSERT_NE(nullptr, attr);
315  ASSERT_NE(nullptr, attr->compiled_value);
316  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
317  ASSERT_TRUE(attr->compiled_attribute);
318}
319
320}  // namespace aapt
321