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 "format/binary/TableFlattener.h"
18 #include <string>
19
20 #include "android-base/stringprintf.h"
21 #include "androidfw/TypeWrappers.h"
22
23 #include "ResChunkPullParser.h"
24 #include "ResourceUtils.h"
25 #include "SdkConstants.h"
26 #include "format/binary/BinaryResourceParser.h"
27 #include "test/Test.h"
28 #include "util/Util.h"
29
30 using namespace android;
31
32 using ::testing::Gt;
33 using ::testing::IsNull;
34 using ::testing::NotNull;
35
36 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
37
38 namespace aapt {
39
40 class TableFlattenerTest : public ::testing::Test {
41 public:
SetUp()42 void SetUp() override {
43 context_ =
44 test::ContextBuilder().SetCompilationPackage("com.app.test").SetPackageId(0x7f).Build();
45 }
46
Flatten(IAaptContext * context,const TableFlattenerOptions & options,ResourceTable * table,std::string * out_content)47 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
48 ResourceTable* table, std::string* out_content) {
49 android::BigBuffer buffer(1024);
50 TableFlattener flattener(options, &buffer);
51 if (!flattener.Consume(context, table)) {
52 return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
53 }
54 *out_content = buffer.to_string();
55 return ::testing::AssertionSuccess();
56 }
57
Flatten(IAaptContext * context,const TableFlattenerOptions & options,ResourceTable * table,ResTable * out_table)58 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
59 ResourceTable* table, ResTable* out_table) {
60 std::string content;
61 auto result = Flatten(context, options, table, &content);
62 if (!result) {
63 return result;
64 }
65
66 if (out_table->add(content.data(), content.size(), 1, true) != NO_ERROR) {
67 return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
68 }
69 return ::testing::AssertionSuccess();
70 }
71
Flatten(IAaptContext * context,const TableFlattenerOptions & options,ResourceTable * table,ResourceTable * out_table)72 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
73 ResourceTable* table, ResourceTable* out_table) {
74 std::string content;
75 auto result = Flatten(context, options, table, &content);
76 if (!result) {
77 return result;
78 }
79
80 BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(),
81 content.size());
82 if (!parser.Parse()) {
83 return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
84 }
85 return ::testing::AssertionSuccess();
86 }
87
Exists(ResTable * table,StringPiece expected_name,const ResourceId & expected_id,const ConfigDescription & expected_config,const uint8_t expected_data_type,const uint32_t expected_data,const uint32_t expected_spec_flags)88 ::testing::AssertionResult Exists(ResTable* table, StringPiece expected_name,
89 const ResourceId& expected_id,
90 const ConfigDescription& expected_config,
91 const uint8_t expected_data_type, const uint32_t expected_data,
92 const uint32_t expected_spec_flags) {
93 const ResourceName expected_res_name = test::ParseNameOrDie(expected_name);
94
95 table->setParameters(&expected_config);
96
97 ResTable_config config;
98 Res_value val;
99 uint32_t spec_flags;
100 if (table->getResource(expected_id.id, &val, false, 0, &spec_flags, &config) < 0) {
101 return ::testing::AssertionFailure() << "could not find resource with";
102 }
103
104 if (expected_data_type != val.dataType) {
105 return ::testing::AssertionFailure()
106 << "expected data type " << std::hex << (int)expected_data_type
107 << " but got data type " << (int)val.dataType << std::dec << " instead";
108 }
109
110 if (expected_data != val.data) {
111 return ::testing::AssertionFailure()
112 << "expected data " << std::hex << expected_data << " but got data " << val.data
113 << std::dec << " instead";
114 }
115
116 if (expected_spec_flags != spec_flags) {
117 return ::testing::AssertionFailure()
118 << "expected specFlags " << std::hex << expected_spec_flags << " but got specFlags "
119 << spec_flags << std::dec << " instead";
120 }
121
122 ResTable::resource_name actual_name;
123 if (!table->getResourceName(expected_id.id, false, &actual_name)) {
124 return ::testing::AssertionFailure() << "failed to find resource name";
125 }
126
127 std::optional<ResourceName> resName = ResourceUtils::ToResourceName(actual_name);
128 if (!resName) {
129 return ::testing::AssertionFailure()
130 << "expected name '" << expected_res_name << "' but got '"
131 << StringPiece16(actual_name.package, actual_name.packageLen) << ":"
132 << StringPiece16(actual_name.type, actual_name.typeLen) << "/"
133 << StringPiece16(actual_name.name, actual_name.nameLen) << "'";
134 }
135
136 ResourceName actual_res_name(resName.value());
137
138 if (expected_res_name.entry != actual_res_name.entry ||
139 expected_res_name.package != actual_res_name.package ||
140 expected_res_name.type != actual_res_name.type) {
141 return ::testing::AssertionFailure() << "expected resource '" << expected_res_name.to_string()
142 << "' but got '" << actual_res_name.to_string() << "'";
143 }
144
145 if (expected_config != config) {
146 return ::testing::AssertionFailure() << "expected config '" << expected_config
147 << "' but got '" << ConfigDescription(config) << "'";
148 }
149 return ::testing::AssertionSuccess();
150 }
151
152 protected:
153 std::unique_ptr<IAaptContext> context_;
154 };
155
TEST_F(TableFlattenerTest,FlattenFullyLinkedTable)156 TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
157 std::unique_ptr<ResourceTable> table =
158 test::ResourceTableBuilder()
159 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
160 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
161 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
162 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
163 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
164 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
165 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
166 ResourceId(0x7f030000),
167 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
168 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
169 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
170 .Build();
171
172 ResTable res_table;
173 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
174
175 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {},
176 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
177
178 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), {},
179 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
180
181 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
182 Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
183
184 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {},
185 Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION));
186
187 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000),
188 test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
189 ResTable_config::CONFIG_VERSION));
190
191 std::u16string foo_str = u"foo";
192 auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
193 ASSERT_TRUE(idx.has_value());
194 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
195 Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
196
197 std::u16string bar_path = u"res/layout/bar.xml";
198 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
199 ASSERT_TRUE(idx.has_value());
200 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", ResourceId(0x7f050000), {},
201 Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
202 }
203
TEST_F(TableFlattenerTest,FlattenEntriesWithGapsInIds)204 TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
205 std::unique_ptr<ResourceTable> table =
206 test::ResourceTableBuilder()
207 .AddSimple("com.app.test:id/one", ResourceId(0x7f020001))
208 .AddSimple("com.app.test:id/three", ResourceId(0x7f020003))
209 .Build();
210
211 ResTable res_table;
212 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
213
214 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), {},
215 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
216 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020003), {},
217 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
218 }
219
TEST_F(TableFlattenerTest,FlattenMinMaxAttributes)220 TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
221 Attribute attr;
222 attr.type_mask = android::ResTable_map::TYPE_INTEGER;
223 attr.min_int = 10;
224 attr.max_int = 23;
225 std::unique_ptr<ResourceTable> table =
226 test::ResourceTableBuilder()
227 .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr))
228 .Build();
229
230 ResourceTable result;
231 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
232
233 Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo");
234 ASSERT_THAT(actual_attr, NotNull());
235 EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak());
236 EXPECT_EQ(attr.type_mask, actual_attr->type_mask);
237 EXPECT_EQ(attr.min_int, actual_attr->min_int);
238 EXPECT_EQ(attr.max_int, actual_attr->max_int);
239 }
240
TEST_F(TableFlattenerTest,FlattenArray)241 TEST_F(TableFlattenerTest, FlattenArray) {
242 auto array = util::make_unique<Array>();
243 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC),
244 1u));
245 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC),
246 2u));
247 std::unique_ptr<ResourceTable> table =
248 test::ResourceTableBuilder()
249 .AddValue("android:array/foo", ResourceId(0x01010000), std::move(array))
250 .Build();
251
252 std::string result;
253 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
254
255 // Parse the flattened resource table
256 ResChunkPullParser parser(result.data(), result.size());
257 ASSERT_TRUE(parser.IsGoodEvent(parser.Next()));
258 ASSERT_EQ(android::util::DeviceToHost16(parser.chunk()->type), RES_TABLE_TYPE);
259
260 // Retrieve the package of the entry
261 ResChunkPullParser table_parser(GetChunkData(parser.chunk()), GetChunkDataLen(parser.chunk()));
262 const ResChunk_header* package_chunk = nullptr;
263 while (table_parser.IsGoodEvent(table_parser.Next())) {
264 if (android::util::DeviceToHost16(table_parser.chunk()->type) == RES_TABLE_PACKAGE_TYPE) {
265 package_chunk = table_parser.chunk();
266 break;
267 }
268 }
269
270 // Retrieve the type that proceeds the array entry
271 ASSERT_NE(package_chunk, nullptr);
272 ResChunkPullParser package_parser(GetChunkData(table_parser.chunk()),
273 GetChunkDataLen(table_parser.chunk()));
274 const ResChunk_header* type_chunk = nullptr;
275 while (package_parser.IsGoodEvent(package_parser.Next())) {
276 if (android::util::DeviceToHost16(package_parser.chunk()->type) == RES_TABLE_TYPE_TYPE) {
277 type_chunk = package_parser.chunk();
278 break;
279 }
280 }
281
282 // Retrieve the array entry
283 ASSERT_NE(type_chunk, nullptr);
284 TypeVariant typeVariant((const ResTable_type*) type_chunk);
285 auto entry = (const ResTable_map_entry*)*typeVariant.beginEntries();
286 ASSERT_EQ(android::util::DeviceToHost16(entry->count), 2u);
287
288 // Check that the value and name of the array entries are correct
289 auto values = (const ResTable_map*)(((const uint8_t *)entry) + entry->size);
290 ASSERT_EQ(values->value.data, 1u);
291 ASSERT_EQ(values->name.ident, android::ResTable_map::ATTR_MIN);
292 ASSERT_EQ((values+1)->value.data, 2u);
293 ASSERT_EQ((values+1)->name.ident, android::ResTable_map::ATTR_MIN + 1);
294 }
295
BuildTableWithSparseEntries(IAaptContext * context,const ConfigDescription & sparse_config,float load)296 static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
297 IAaptContext* context, const ConfigDescription& sparse_config, float load) {
298 std::unique_ptr<ResourceTable> table =
299 test::ResourceTableBuilder()
300 .Build();
301
302 // Add regular entries.
303 CloningValueTransformer cloner(&table->string_pool);
304 int stride = static_cast<int>(1.0f / load);
305 for (int i = 0; i < 100; i++) {
306 const ResourceName name = test::ParseNameOrDie(
307 base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i));
308 const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
309 const auto value =
310 util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
311 CHECK(table->AddResource(NewResourceBuilder(name)
312 .SetId(resid)
313 .SetValue(std::unique_ptr<Value>(value->Transform(cloner)))
314 .Build(),
315 context->GetDiagnostics()));
316
317 // Every few entries, write out a sparse_config value. This will give us the desired load.
318 if (i % stride == 0) {
319 CHECK(table->AddResource(
320 NewResourceBuilder(name)
321 .SetId(resid)
322 .SetValue(std::unique_ptr<Value>(value->Transform(cloner)), sparse_config)
323 .Build(),
324 context->GetDiagnostics()));
325 }
326 }
327 return table;
328 }
329
CheckSparseEntries(IAaptContext * context,const ConfigDescription & sparse_config,const std::string & sparse_contents)330 static void CheckSparseEntries(IAaptContext* context, const ConfigDescription& sparse_config,
331 const std::string& sparse_contents) {
332 ResourceTable sparse_table;
333 BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
334 sparse_contents.data(), sparse_contents.size());
335 ASSERT_TRUE(parser.Parse());
336
337 auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
338 sparse_config);
339 ASSERT_THAT(value, NotNull());
340 EXPECT_EQ(0u, value->value.data);
341
342 ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
343 sparse_config),
344 IsNull());
345
346 value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
347 sparse_config);
348 ASSERT_THAT(value, NotNull());
349 EXPECT_EQ(4u, value->value.data);
350 }
351
TEST_F(TableFlattenerTest,FlattenSparseEntryWithMinSdkSV2)352 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
353 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
354 .SetCompilationPackage("android")
355 .SetPackageId(0x01)
356 .SetMinSdkVersion(SDK_S_V2)
357 .Build();
358
359 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
360 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
361
362 TableFlattenerOptions options;
363 options.sparse_entries = SparseEntriesMode::Enabled;
364
365 std::string no_sparse_contents;
366 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
367
368 std::string sparse_contents;
369 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
370
371 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
372
373 CheckSparseEntries(context.get(), sparse_config, sparse_contents);
374 }
375
TEST_F(TableFlattenerTest,FlattenSparseEntryWithMinSdkSV2AndForced)376 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2AndForced) {
377 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
378 .SetCompilationPackage("android")
379 .SetPackageId(0x01)
380 .SetMinSdkVersion(SDK_S_V2)
381 .Build();
382
383 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
384 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
385
386 TableFlattenerOptions options;
387 options.sparse_entries = SparseEntriesMode::Forced;
388
389 std::string no_sparse_contents;
390 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
391
392 std::string sparse_contents;
393 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
394
395 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
396
397 CheckSparseEntries(context.get(), sparse_config, sparse_contents);
398 }
399
TEST_F(TableFlattenerTest,FlattenSparseEntryWithMinSdkBeforeSV2)400 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2) {
401 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
402 .SetCompilationPackage("android")
403 .SetPackageId(0x01)
404 .SetMinSdkVersion(SDK_LOLLIPOP)
405 .Build();
406
407 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
408 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
409
410 TableFlattenerOptions options;
411 options.sparse_entries = SparseEntriesMode::Enabled;
412
413 std::string no_sparse_contents;
414 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
415
416 std::string sparse_contents;
417 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
418
419 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
420 }
421
TEST_F(TableFlattenerTest,FlattenSparseEntryWithMinSdkBeforeSV2AndConfigSdkVersionSV2)422 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndConfigSdkVersionSV2) {
423 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
424 .SetCompilationPackage("android")
425 .SetPackageId(0x01)
426 .SetMinSdkVersion(SDK_LOLLIPOP)
427 .Build();
428
429 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v32");
430 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
431
432 TableFlattenerOptions options;
433 options.sparse_entries = SparseEntriesMode::Enabled;
434
435 std::string no_sparse_contents;
436 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
437
438 std::string sparse_contents;
439 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
440
441 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
442 }
443
TEST_F(TableFlattenerTest,FlattenSparseEntryWithMinSdkBeforeSV2AndForced)444 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndForced) {
445 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
446 .SetCompilationPackage("android")
447 .SetPackageId(0x01)
448 .SetMinSdkVersion(SDK_LOLLIPOP)
449 .Build();
450
451 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
452 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
453
454 TableFlattenerOptions options;
455 options.sparse_entries = SparseEntriesMode::Forced;
456
457 std::string no_sparse_contents;
458 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
459
460 std::string sparse_contents;
461 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
462
463 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
464 }
465
TEST_F(TableFlattenerTest,FlattenSparseEntryWithSdkVersionNotSet)466 TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
467 std::unique_ptr<IAaptContext> context =
468 test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
469
470 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
471 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
472
473 TableFlattenerOptions options;
474 options.sparse_entries = SparseEntriesMode::Enabled;
475
476 std::string no_sparse_contents;
477 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
478
479 std::string sparse_contents;
480 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
481
482 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
483 }
484
TEST_F(TableFlattenerTest,FlattenSparseEntryWithSdkVersionNotSetAndForced)485 TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSetAndForced) {
486 std::unique_ptr<IAaptContext> context =
487 test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
488
489 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
490 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
491
492 TableFlattenerOptions options;
493 options.sparse_entries = SparseEntriesMode::Forced;
494
495 std::string no_sparse_contents;
496 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
497
498 std::string sparse_contents;
499 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
500
501 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
502
503 CheckSparseEntries(context.get(), sparse_config, sparse_contents);
504 }
505
TEST_F(TableFlattenerTest,DoNotUseSparseEntryForDenseConfig)506 TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
507 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
508 .SetCompilationPackage("android")
509 .SetPackageId(0x01)
510 .SetMinSdkVersion(SDK_O)
511 .Build();
512
513 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
514 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
515
516 TableFlattenerOptions options;
517 options.sparse_entries = SparseEntriesMode::Enabled;
518
519 std::string no_sparse_contents;
520 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
521
522 std::string sparse_contents;
523 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
524
525 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
526 }
527
TEST_F(TableFlattenerTest,FlattenSharedLibrary)528 TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
529 std::unique_ptr<IAaptContext> context =
530 test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
531 std::unique_ptr<ResourceTable> table =
532 test::ResourceTableBuilder()
533 .AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>())
534 .Build();
535 ResourceTable result;
536 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
537
538 std::optional<ResourceTable::SearchResult> search_result =
539 result.FindResource(test::ParseNameOrDie("lib:id/foo"));
540 ASSERT_TRUE(search_result);
541 EXPECT_EQ(0x00u, search_result.value().entry->id.value().package_id());
542
543 auto iter = result.included_packages_.find(0x00);
544 ASSERT_NE(result.included_packages_.end(), iter);
545 EXPECT_EQ("lib", iter->second);
546 }
547
TEST_F(TableFlattenerTest,FlattenSharedLibraryWithStyle)548 TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
549 std::unique_ptr<IAaptContext> context =
550 test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
551 std::unique_ptr<ResourceTable> table =
552 test::ResourceTableBuilder()
553 .AddValue("lib:style/Theme",
554 ResourceId(0x00030001),
555 test::StyleBuilder()
556 .AddItem("lib:attr/bar", ResourceId(0x00010002),
557 ResourceUtils::TryParseInt("2"))
558 .AddItem("lib:attr/foo", ResourceId(0x00010001),
559 ResourceUtils::TryParseInt("1"))
560 .AddItem("android:attr/bar", ResourceId(0x01010002),
561 ResourceUtils::TryParseInt("4"))
562 .AddItem("android:attr/foo", ResourceId(0x01010001),
563 ResourceUtils::TryParseInt("3"))
564 .Build())
565 .Build();
566 ResourceTable result;
567 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
568
569 std::optional<ResourceTable::SearchResult> search_result =
570 result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
571 ASSERT_TRUE(search_result);
572 EXPECT_EQ(0x00030001u, search_result.value().entry->id.value());
573 ASSERT_EQ(1u, search_result.value().entry->values.size());
574 Value* value = search_result.value().entry->values[0]->value.get();
575 Style* style = ValueCast<Style>(value);
576 ASSERT_TRUE(style);
577 ASSERT_EQ(4u, style->entries.size());
578 // Ensure the attributes from the shared library come after the items from
579 // android.
580 EXPECT_EQ(0x01010001, style->entries[0].key.id.value());
581 EXPECT_EQ(0x01010002, style->entries[1].key.id.value());
582 EXPECT_EQ(0x00010001, style->entries[2].key.id.value());
583 EXPECT_EQ(0x00010002, style->entries[3].key.id.value());
584 }
585
TEST_F(TableFlattenerTest,FlattenTableReferencingSharedLibraries)586 TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
587 std::unique_ptr<IAaptContext> context =
588 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
589 std::unique_ptr<ResourceTable> table =
590 test::ResourceTableBuilder()
591 .AddValue("app:id/foo", ResourceId(0x7f010000),
592 test::BuildReference("lib_one:id/foo", ResourceId(0x02010000)))
593 .AddValue("app:id/bar", ResourceId(0x7f010001),
594 test::BuildReference("lib_two:id/bar", ResourceId(0x03010000)))
595 .Build();
596 table->included_packages_[0x02] = "lib_one";
597 table->included_packages_[0x03] = "lib_two";
598
599 ResTable result;
600 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
601
602 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
603 ASSERT_THAT(dynamic_ref_table, NotNull());
604
605 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
606
607 ssize_t idx = entries.indexOfKey(android::String16("lib_one"));
608 ASSERT_GE(idx, 0);
609 EXPECT_EQ(0x02u, entries.valueAt(idx));
610
611 idx = entries.indexOfKey(android::String16("lib_two"));
612 ASSERT_GE(idx, 0);
613 EXPECT_EQ(0x03u, entries.valueAt(idx));
614 }
615
TEST_F(TableFlattenerTest,PackageWithNonStandardIdHasDynamicRefTable)616 TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) {
617 std::unique_ptr<IAaptContext> context =
618 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build();
619 std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
620 .AddSimple("app:id/foo", ResourceId(0x80010000))
621 .Build();
622
623 ResTable result;
624 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
625
626 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
627 ASSERT_THAT(dynamic_ref_table, NotNull());
628
629 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
630 ssize_t idx = entries.indexOfKey(android::String16("app"));
631 ASSERT_GE(idx, 0);
632 EXPECT_EQ(0x80u, entries.valueAt(idx));
633 }
634
TEST_F(TableFlattenerTest,LongPackageNameIsTruncated)635 TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) {
636 std::string kPackageName(256, 'F');
637
638 std::unique_ptr<IAaptContext> context =
639 test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build();
640 std::unique_ptr<ResourceTable> table =
641 test::ResourceTableBuilder()
642 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
643 .Build();
644
645 ResTable result;
646 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
647
648 ASSERT_EQ(1u, result.getBasePackageCount());
649 EXPECT_EQ(127u, result.getBasePackageName(0).size());
650 }
651
TEST_F(TableFlattenerTest,LongSharedLibraryPackageNameIsIllegal)652 TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
653 std::string kPackageName(256, 'F');
654
655 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
656 .SetCompilationPackage(kPackageName)
657 .SetPackageId(0x7f)
658 .SetPackageType(PackageType::kSharedLib)
659 .Build();
660 std::unique_ptr<ResourceTable> table =
661 test::ResourceTableBuilder()
662 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
663 .Build();
664
665 ResTable result;
666 ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
667 }
668
TEST_F(TableFlattenerTest,ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds)669 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
670 std::unique_ptr<ResourceTable> table =
671 test::ResourceTableBuilder()
672 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
673 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
674 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
675 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
676 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
677 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
678 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
679 ResourceId(0x7f030000),
680 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
681 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
682 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
683 .Build();
684
685 TableFlattenerOptions options;
686 options.collapse_key_stringpool = true;
687
688 ResTable res_table;
689
690 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
691
692 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
693 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
694
695 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
696 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
697
698 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
699 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
700
701 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
702 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
703 ResTable_config::CONFIG_VERSION));
704
705 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
706 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
707 2u, ResTable_config::CONFIG_VERSION));
708
709 std::u16string foo_str = u"foo";
710 auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
711 ASSERT_TRUE(idx.has_value());
712 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
713 ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
714
715 std::u16string bar_path = u"res/layout/bar.xml";
716 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
717 ASSERT_TRUE(idx.has_value());
718 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
719 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
720 }
721
TEST_F(TableFlattenerTest,ObfuscatingResourceNamesWithDeduplicationSucceeds)722 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithDeduplicationSucceeds) {
723 std::unique_ptr<ResourceTable> table =
724 test::ResourceTableBuilder()
725 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
726 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
727 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
728 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
729 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
730 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
731 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
732 ResourceId(0x7f030000),
733 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
734 .AddString("com.app.test:string/test1", ResourceId(0x7f040000), "foo")
735 .AddString("com.app.test:string/test2", ResourceId(0x7f040001), "foo")
736 .AddString("com.app.test:string/test3", ResourceId(0x7f040002), "bar")
737 .AddString("com.app.test:string/test4", ResourceId(0x7f040003), "foo")
738 .AddString("com.app.test:layout/bar1", ResourceId(0x7f050000), "res/layout/bar.xml")
739 .AddString("com.app.test:layout/bar2", ResourceId(0x7f050001), "res/layout/bar.xml")
740 .Build();
741
742 TableFlattenerOptions options;
743 options.collapse_key_stringpool = true;
744 options.deduplicate_entry_values = true;
745
746 ResTable res_table;
747
748 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
749
750 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
751 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
752
753 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
754 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
755
756 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
757 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
758
759 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
760 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
761 ResTable_config::CONFIG_VERSION));
762
763 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
764 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
765 2u, ResTable_config::CONFIG_VERSION));
766
767 std::u16string foo_str = u"foo";
768 std::u16string bar_str = u"bar";
769 auto foo_idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
770 auto bar_idx = res_table.getTableStringBlock(0)->indexOfString(bar_str.data(), bar_str.size());
771 ASSERT_TRUE(foo_idx.has_value());
772 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
773 ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u));
774 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
775 ResourceId(0x7f040001), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u));
776 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
777 ResourceId(0x7f040002), {}, Res_value::TYPE_STRING, (uint32_t)*bar_idx, 0u));
778 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
779 ResourceId(0x7f040003), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u));
780
781 std::u16string bar_path = u"res/layout/bar.xml";
782 auto bar_path_idx =
783 res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
784 ASSERT_TRUE(bar_path_idx.has_value());
785 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
786 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*bar_path_idx,
787 0u));
788 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
789 ResourceId(0x7f050001), {}, Res_value::TYPE_STRING, (uint32_t)*bar_path_idx,
790 0u));
791
792 std::string deduplicated_output;
793 std::string sequential_output;
794 Flatten(context_.get(), options, table.get(), &deduplicated_output);
795 options.deduplicate_entry_values = false;
796 Flatten(context_.get(), options, table.get(), &sequential_output);
797
798 // We have 4 duplicates: 0x7f020001 id, 0x7f040001 string, 0x7f040003 string, 0x7f050001 layout.
799 EXPECT_EQ(sequential_output.size(),
800 deduplicated_output.size() + 4 * (sizeof(ResTable_entry) + sizeof(Res_value)));
801 }
802
TEST_F(TableFlattenerTest,ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds)803 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
804 std::unique_ptr<ResourceTable> table =
805 test::ResourceTableBuilder()
806 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
807 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
808 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
809 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
810 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
811 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
812 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
813 ResourceId(0x7f030000),
814 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
815 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
816 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
817 .Build();
818
819 TableFlattenerOptions options;
820 options.collapse_key_stringpool = true;
821 options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
822 options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
823 ResTable res_table;
824
825 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
826
827 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one",
828 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
829
830 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
831 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
832
833 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
834 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
835
836 // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
837 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
838 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
839 ResTable_config::CONFIG_VERSION));
840
841 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
842 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
843 2u, ResTable_config::CONFIG_VERSION));
844
845 std::u16string foo_str = u"foo";
846 auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
847 ASSERT_TRUE(idx.has_value());
848 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
849 Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
850
851 std::u16string bar_path = u"res/layout/bar.xml";
852 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
853 ASSERT_TRUE(idx.has_value());
854 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
855 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
856 }
857
TEST_F(TableFlattenerTest,FlattenOverlayable)858 TEST_F(TableFlattenerTest, FlattenOverlayable) {
859 OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
860 overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
861 overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
862 overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
863
864 std::string name = "com.app.test:integer/overlayable";
865 std::unique_ptr<ResourceTable> table =
866 test::ResourceTableBuilder()
867 .AddSimple(name, ResourceId(0x7f020000))
868 .SetOverlayable(name, overlayable_item)
869 .Build();
870
871 ResourceTable output_table;
872 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
873
874 auto search_result = output_table.FindResource(test::ParseNameOrDie(name));
875 ASSERT_TRUE(search_result);
876 ASSERT_THAT(search_result.value().entry, NotNull());
877 ASSERT_TRUE(search_result.value().entry->overlayable_item);
878 OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
879 EXPECT_EQ(result_overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
880 | PolicyFlags::VENDOR_PARTITION
881 | PolicyFlags::PRODUCT_PARTITION);
882 }
883
TEST_F(TableFlattenerTest,FlattenMultipleOverlayablePolicies)884 TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
885 auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme");
886 std::string name_zero = "com.app.test:integer/overlayable_zero_item";
887 OverlayableItem overlayable_item_zero(overlayable);
888 overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
889 overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
890
891 std::string name_one = "com.app.test:integer/overlayable_one_item";
892 OverlayableItem overlayable_item_one(overlayable);
893 overlayable_item_one.policies |= PolicyFlags::PUBLIC;
894
895 std::string name_two = "com.app.test:integer/overlayable_two_item";
896 OverlayableItem overlayable_item_two(overlayable);
897 overlayable_item_two.policies |= PolicyFlags::PRODUCT_PARTITION;
898 overlayable_item_two.policies |= PolicyFlags::SYSTEM_PARTITION;
899 overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
900
901 std::unique_ptr<ResourceTable> table =
902 test::ResourceTableBuilder()
903 .AddSimple(name_zero, ResourceId(0x7f020000))
904 .SetOverlayable(name_zero, overlayable_item_zero)
905 .AddSimple(name_one, ResourceId(0x7f020001))
906 .SetOverlayable(name_one, overlayable_item_one)
907 .AddSimple(name_two, ResourceId(0x7f020002))
908 .SetOverlayable(name_two, overlayable_item_two)
909 .Build();
910
911 ResourceTable output_table;
912 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
913
914 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
915 ASSERT_TRUE(search_result);
916 ASSERT_THAT(search_result.value().entry, NotNull());
917 ASSERT_TRUE(search_result.value().entry->overlayable_item);
918 OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value();
919 EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
920 | PolicyFlags::PRODUCT_PARTITION);
921
922 search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
923 ASSERT_TRUE(search_result);
924 ASSERT_THAT(search_result.value().entry, NotNull());
925 ASSERT_TRUE(search_result.value().entry->overlayable_item);
926 overlayable_item = search_result.value().entry->overlayable_item.value();
927 EXPECT_EQ(overlayable_item.policies, PolicyFlags::PUBLIC);
928
929 search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
930 ASSERT_TRUE(search_result);
931 ASSERT_THAT(search_result.value().entry, NotNull());
932 ASSERT_TRUE(search_result.value().entry->overlayable_item);
933 overlayable_item = search_result.value().entry->overlayable_item.value();
934 EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
935 | PolicyFlags::PRODUCT_PARTITION
936 | PolicyFlags::VENDOR_PARTITION);
937 }
938
TEST_F(TableFlattenerTest,FlattenMultipleOverlayable)939 TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
940 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
941 std::string name_zero = "com.app.test:integer/overlayable_zero";
942 OverlayableItem overlayable_item_zero(group);
943 overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
944 overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
945
946 auto group_one = std::make_shared<Overlayable>("OtherName", "overlay://customization");
947 std::string name_one = "com.app.test:integer/overlayable_one";
948 OverlayableItem overlayable_item_one(group_one);
949 overlayable_item_one.policies |= PolicyFlags::PUBLIC;
950
951 std::string name_two = "com.app.test:integer/overlayable_two";
952 OverlayableItem overlayable_item_two(group);
953 overlayable_item_two.policies |= PolicyFlags::ODM_PARTITION;
954 overlayable_item_two.policies |= PolicyFlags::OEM_PARTITION;
955 overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
956
957 std::string name_three = "com.app.test:integer/overlayable_three";
958 OverlayableItem overlayable_item_three(group_one);
959 overlayable_item_three.policies |= PolicyFlags::SIGNATURE;
960 overlayable_item_three.policies |= PolicyFlags::ACTOR_SIGNATURE;
961 overlayable_item_three.policies |= PolicyFlags::CONFIG_SIGNATURE;
962
963 std::unique_ptr<ResourceTable> table =
964 test::ResourceTableBuilder()
965 .AddSimple(name_zero, ResourceId(0x7f020000))
966 .SetOverlayable(name_zero, overlayable_item_zero)
967 .AddSimple(name_one, ResourceId(0x7f020001))
968 .SetOverlayable(name_one, overlayable_item_one)
969 .AddSimple(name_two, ResourceId(0x7f020002))
970 .SetOverlayable(name_two, overlayable_item_two)
971 .AddSimple(name_three, ResourceId(0x7f020003))
972 .SetOverlayable(name_three, overlayable_item_three)
973 .Build();
974
975 ResourceTable output_table;
976 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
977 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
978 ASSERT_TRUE(search_result);
979 ASSERT_THAT(search_result.value().entry, NotNull());
980 ASSERT_TRUE(search_result.value().entry->overlayable_item);
981 OverlayableItem& result_overlayable = search_result.value().entry->overlayable_item.value();
982 EXPECT_EQ(result_overlayable.overlayable->name, "TestName");
983 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
984 EXPECT_EQ(result_overlayable.policies, PolicyFlags::SYSTEM_PARTITION
985 | PolicyFlags::PRODUCT_PARTITION);
986
987 search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
988 ASSERT_TRUE(search_result);
989 ASSERT_THAT(search_result.value().entry, NotNull());
990 ASSERT_TRUE(search_result.value().entry->overlayable_item);
991 result_overlayable = search_result.value().entry->overlayable_item.value();
992 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
993 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
994 EXPECT_EQ(result_overlayable.policies, PolicyFlags::PUBLIC);
995
996 search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
997 ASSERT_TRUE(search_result);
998 ASSERT_THAT(search_result.value().entry, NotNull());
999 ASSERT_TRUE(search_result.value().entry->overlayable_item);
1000 result_overlayable = search_result.value().entry->overlayable_item.value();
1001 EXPECT_EQ(result_overlayable.overlayable->name, "TestName");
1002 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
1003 EXPECT_EQ(result_overlayable.policies, PolicyFlags::ODM_PARTITION
1004 | PolicyFlags::OEM_PARTITION
1005 | PolicyFlags::VENDOR_PARTITION);
1006
1007 search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
1008 ASSERT_TRUE(search_result);
1009 ASSERT_THAT(search_result.value().entry, NotNull());
1010 ASSERT_TRUE(search_result.value().entry->overlayable_item);
1011 result_overlayable = search_result.value().entry->overlayable_item.value();
1012 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
1013 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
1014 EXPECT_EQ(result_overlayable.policies, PolicyFlags::SIGNATURE
1015 | PolicyFlags::ACTOR_SIGNATURE
1016 | PolicyFlags::CONFIG_SIGNATURE);
1017 }
1018
TEST_F(TableFlattenerTest,FlattenOverlayableNoPolicyFails)1019 TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) {
1020 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
1021 std::string name_zero = "com.app.test:integer/overlayable_zero";
1022 OverlayableItem overlayable_item_zero(group);
1023
1024 std::unique_ptr<ResourceTable> table =
1025 test::ResourceTableBuilder()
1026 .AddSimple(name_zero, ResourceId(0x7f020000))
1027 .SetOverlayable(name_zero, overlayable_item_zero)
1028 .Build();
1029 ResourceTable output_table;
1030 ASSERT_FALSE(Flatten(context_.get(), {}, table.get(), &output_table));
1031 }
1032
TEST_F(TableFlattenerTest,FlattenCustomResourceTypes)1033 TEST_F(TableFlattenerTest, FlattenCustomResourceTypes) {
1034 std::unique_ptr<ResourceTable> table =
1035 test::ResourceTableBuilder()
1036 .AddSimple("com.app.test:id/one", ResourceId(0x7f010000))
1037 .AddSimple("com.app.test:id.2/two", ResourceId(0x7f020000))
1038 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
1039 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 10u))
1040 .AddValue("com.app.test:integer.1/one", ResourceId(0x7f040000),
1041 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
1042 .AddValue("com.app.test:integer.1/one", test::ParseConfigOrDie("v1"),
1043 ResourceId(0x7f040000),
1044 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
1045 .AddString("com.app.test:layout.custom/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
1046 .Build();
1047
1048 ResTable res_table;
1049 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
1050
1051 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f010000), {},
1052 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
1053
1054 EXPECT_TRUE(Exists(&res_table, "com.app.test:id.2/two", ResourceId(0x7f020000), {},
1055 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
1056
1057 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {},
1058 Res_value::TYPE_INT_DEC, 10u, 0u));
1059
1060 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer.1/one", ResourceId(0x7f040000), {},
1061 Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION));
1062
1063 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer.1/one", ResourceId(0x7f040000),
1064 test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
1065 ResTable_config::CONFIG_VERSION));
1066
1067 std::u16string bar_path = u"res/layout/bar.xml";
1068 auto idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
1069 ASSERT_TRUE(idx.has_value());
1070 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout.custom/bar", ResourceId(0x7f050000), {},
1071 Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
1072 }
1073
TEST_F(TableFlattenerTest,FlattenTypeEntryWithNameCollapseNotInExemption)1074 TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseNotInExemption) {
1075 OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
1076 overlayable_item.policies |= PolicyFlags::PUBLIC;
1077
1078 std::string name = "com.app.test:color/overlayable_color";
1079 std::unique_ptr<ResourceTable> table =
1080 test::ResourceTableBuilder()
1081 .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000),
1082 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8),
1083 0xffaabbcc))
1084 .SetOverlayable(name, overlayable_item)
1085 .Build();
1086
1087 TableFlattenerOptions options;
1088 options.collapse_key_stringpool = true;
1089
1090 ResTable res_table;
1091 EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue());
1092 EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {},
1093 Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u),
1094 testing::IsTrue());
1095 }
1096
TEST_F(TableFlattenerTest,FlattenTypeEntryWithNameCollapseInExemption)1097 TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseInExemption) {
1098 OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
1099 overlayable_item.policies |= PolicyFlags::PUBLIC;
1100
1101 std::string name = "com.app.test:color/overlayable_color";
1102 std::unique_ptr<ResourceTable> table =
1103 test::ResourceTableBuilder()
1104 .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000),
1105 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8),
1106 0xffaabbcc))
1107 .SetOverlayable(name, overlayable_item)
1108 .Build();
1109
1110 TableFlattenerOptions options;
1111 options.collapse_key_stringpool = true;
1112 options.name_collapse_exemptions.insert(
1113 ResourceName({}, ResourceType::kColor, "overlayable_color"));
1114
1115 ResTable res_table;
1116 EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue());
1117 EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {},
1118 Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u),
1119 testing::IsTrue());
1120 }
1121
TEST_F(TableFlattenerTest,UsesReadWriteFeatureFlagSerializesCorrectly)1122 TEST_F(TableFlattenerTest, UsesReadWriteFeatureFlagSerializesCorrectly) {
1123 std::unique_ptr<ResourceTable> table =
1124 test::ResourceTableBuilder()
1125 .Add(NewResourceBuilder("com.app.a:color/foo")
1126 .SetValue(util::make_unique<BinaryPrimitive>(
1127 uint8_t(Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
1128 .SetUsesReadWriteFeatureFlags(true)
1129 .SetId(0x7f020000)
1130 .Build())
1131 .Build();
1132 ResTable res_table;
1133 TableFlattenerOptions options;
1134 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
1135
1136 uint32_t flags;
1137 ASSERT_TRUE(res_table.getResourceEntryFlags(0x7f020000, &flags));
1138 ASSERT_EQ(flags, ResTable_entry::FLAG_USES_FEATURE_FLAGS);
1139 }
1140
1141 } // namespace aapt
1142