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