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