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