Enable --collapse-resource-names on bundles

This cl enables aapt2 optimize and convert to handle collapsed resource
names optimization.

Test: make aapt2_test

Change-Id: I160d7e5bbd94580b52c00b648918e47beb4674f1
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 45719ef..e930b47 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -267,8 +267,14 @@
         return false;
       }
     } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
+      SerializeTableOptions proto_serialize_options;
+      proto_serialize_options.collapse_key_stringpool =
+          options.collapse_key_stringpool;
+      proto_serialize_options.name_collapse_exemptions =
+          options.name_collapse_exemptions;
       pb::ResourceTable pb_table;
-      SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics());
+      SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics(),
+                         proto_serialize_options);
       if (!io::CopyProtoToArchive(context,
                                   &pb_table,
                                   path,
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 2fd01d7..828715d 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -446,9 +446,12 @@
     }
 
     for (const pb::Entry& pb_entry : pb_type.entry()) {
-      ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name());
+      ResourceEntry* entry;
       if (pb_entry.has_entry_id()) {
-        entry->id = static_cast<uint16_t>(pb_entry.entry_id().id());
+        auto entry_id = static_cast<uint16_t>(pb_entry.entry_id().id());
+        entry = type->FindOrCreateEntry(pb_entry.name(), entry_id);
+      } else {
+        entry = type->FindOrCreateEntry(pb_entry.name());
       }
 
       // Deserialize the symbol status (public/private with source and comments).
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index ba6df22..5ab43b7 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -356,12 +356,21 @@
       }
       pb_type->set_name(to_string(type->type).to_string());
 
+      // hardcoded string uses characters which make it an invalid resource name
+      static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
       for (const std::unique_ptr<ResourceEntry>& entry : type->entries) {
         pb::Entry* pb_entry = pb_type->add_entry();
         if (entry->id) {
           pb_entry->mutable_entry_id()->set_id(entry->id.value());
         }
-        pb_entry->set_name(entry->name);
+        ResourceName resource_name({}, type->type, entry->name);
+        if (options.collapse_key_stringpool &&
+            options.name_collapse_exemptions.find(resource_name) ==
+            options.name_collapse_exemptions.end()) {
+          pb_entry->set_name(obfuscated_resource_name);
+        } else {
+          pb_entry->set_name(entry->name);
+        }
 
         // Write the Visibility struct.
         pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h
index 7a3ea99..b0d5630 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.h
+++ b/tools/aapt2/format/proto/ProtoSerialize.h
@@ -38,6 +38,15 @@
 struct SerializeTableOptions {
     /** Prevent serializing the source pool and source protos.  */
     bool exclude_sources = false;
+
+    // When true, all the entry names in pb:ResourceTable are collapsed to a
+    // single entry name. When the proto table is converted to binary
+    // resources.arsc, the key string pool is collapsed to a single entry. All
+    // resource entries have name indices that point to this single value.
+    bool collapse_key_stringpool = false;
+
+    // Set of resources to avoid collapsing to a single entry in key stringpool.
+    std::set<ResourceName> name_collapse_exemptions;
 };
 
 // Serializes a Value to its protobuf representation. An optional StringPool will hold the
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 1a7de6d..fe4c8aa 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -24,6 +24,7 @@
 using ::android::StringPiece;
 using ::testing::Eq;
 using ::testing::IsEmpty;
+using ::testing::IsNull;
 using ::testing::NotNull;
 using ::testing::SizeIs;
 using ::testing::StrEq;
@@ -39,6 +40,13 @@
   MOCK_METHOD0(GetDirSeparator, char());
 };
 
+ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name,
+                   uint32_t id) {
+  ResourceTablePackage* package = table->FindPackage(res_name.package);
+  ResourceTableType* type = package->FindType(res_name.type);
+  return  type->FindEntry(res_name.entry, id);
+}
+
 TEST(ProtoSerializeTest, SerializeSinglePackage) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   std::unique_ptr<ResourceTable> table =
@@ -662,4 +670,167 @@
   EXPECT_FALSE(actual_ref->is_dynamic);
 }
 
+TEST(ProtoSerializeTest, CollapsingResourceNamesNoNameCollapseExemptionsSucceeds) {
+  const uint32_t id_one_id = 0x7f020000;
+  const uint32_t id_two_id = 0x7f020001;
+  const uint32_t id_three_id = 0x7f020002;
+  const uint32_t integer_three_id = 0x7f030000;
+  const uint32_t string_test_id = 0x7f040000;
+  const uint32_t layout_bar_id = 0x7f050000;
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.test", 0x7f)
+          .AddSimple("com.app.test:id/one", ResourceId(id_one_id))
+          .AddSimple("com.app.test:id/two", ResourceId(id_two_id))
+          .AddValue("com.app.test:id/three", ResourceId(id_three_id),
+                    test::BuildReference("com.app.test:id/one", ResourceId(id_one_id)))
+          .AddValue("com.app.test:integer/one", ResourceId(integer_three_id),
+                    util::make_unique<BinaryPrimitive>(
+                        uint8_t(android::Res_value::TYPE_INT_DEC), 1u))
+          .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+                    ResourceId(integer_three_id),
+                    util::make_unique<BinaryPrimitive>(
+                        uint8_t(android::Res_value::TYPE_INT_DEC), 2u))
+          .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo")
+          .AddFileReference("com.app.test:layout/bar", ResourceId(layout_bar_id),
+                            "res/layout/bar.xml")
+          .Build();
+
+  SerializeTableOptions options;
+  options.collapse_key_stringpool = true;
+
+  pb::ResourceTable pb_table;
+
+  SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options);
+  test::TestFile file_a("res/layout/bar.xml");
+  MockFileCollection files;
+  EXPECT_CALL(files, FindFile(Eq("res/layout/bar.xml")))
+      .WillRepeatedly(::testing::Return(&file_a));
+  ResourceTable new_table;
+  std::string error;
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error;
+  EXPECT_THAT(error, IsEmpty());
+
+  ResourceName real_id_resource(
+      "com.app.test", ResourceType::kId, "one");
+  EXPECT_THAT(GetEntry(&new_table, real_id_resource, id_one_id), IsNull());
+
+  ResourceName obfuscated_id_resource(
+      "com.app.test", ResourceType::kId, "0_resource_name_obfuscated");
+
+  EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource,
+                  id_one_id), NotNull());
+  EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource,
+                  id_two_id), NotNull());
+  ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id);
+  EXPECT_THAT(entry, NotNull());
+  ResourceConfigValue* config_value = entry->FindValue({});
+  Reference* ref = ValueCast<Reference>(config_value->value.get());
+  EXPECT_THAT(ref->id.value(), Eq(id_one_id));
+
+  ResourceName obfuscated_integer_resource(
+      "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated");
+  entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id);
+  EXPECT_THAT(entry, NotNull());
+  config_value = entry->FindValue({});
+  BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+  EXPECT_THAT(bp->value.data, Eq(1u));
+
+  config_value = entry->FindValue(test::ParseConfigOrDie("v1"));
+  bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+  EXPECT_THAT(bp->value.data, Eq(2u));
+
+  ResourceName obfuscated_string_resource(
+      "com.app.test", ResourceType::kString, "0_resource_name_obfuscated");
+  entry = GetEntry(&new_table, obfuscated_string_resource, string_test_id);
+  EXPECT_THAT(entry, NotNull());
+  config_value = entry->FindValue({});
+  String* s = ValueCast<String>(config_value->value.get());
+  EXPECT_THAT(*(s->value), Eq("foo"));
+
+  ResourceName obfuscated_layout_resource(
+      "com.app.test", ResourceType::kLayout, "0_resource_name_obfuscated");
+  entry = GetEntry(&new_table, obfuscated_layout_resource, layout_bar_id);
+  EXPECT_THAT(entry, NotNull());
+  config_value = entry->FindValue({});
+  FileReference* f = ValueCast<FileReference>(config_value->value.get());
+  EXPECT_THAT(*(f->path), Eq("res/layout/bar.xml"));
+}
+
+TEST(ProtoSerializeTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
+  const uint32_t id_one_id = 0x7f020000;
+  const uint32_t id_two_id = 0x7f020001;
+  const uint32_t id_three_id = 0x7f020002;
+  const uint32_t integer_three_id = 0x7f030000;
+  const uint32_t string_test_id = 0x7f040000;
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.test", 0x7f)
+          .AddSimple("com.app.test:id/one", ResourceId(id_one_id))
+          .AddSimple("com.app.test:id/two", ResourceId(id_two_id))
+          .AddValue("com.app.test:id/three", ResourceId(id_three_id),
+                    test::BuildReference("com.app.test:id/one", ResourceId(id_one_id)))
+          .AddValue("com.app.test:integer/one", ResourceId(integer_three_id),
+                    util::make_unique<BinaryPrimitive>(
+                        uint8_t(android::Res_value::TYPE_INT_DEC), 1u))
+          .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+                    ResourceId(integer_three_id),
+                    util::make_unique<BinaryPrimitive>(
+                        uint8_t(android::Res_value::TYPE_INT_DEC), 2u))
+          .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo")
+          .Build();
+
+  SerializeTableOptions options;
+  options.collapse_key_stringpool = true;
+  options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
+  options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
+  pb::ResourceTable pb_table;
+
+  SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options);
+  MockFileCollection files;
+  ResourceTable new_table;
+  std::string error;
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error;
+  EXPECT_THAT(error, IsEmpty());
+
+  EXPECT_THAT(GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kId, "one"),
+                       id_one_id), NotNull());
+  ResourceName obfuscated_id_resource(
+      "com.app.test", ResourceType::kId, "0_resource_name_obfuscated");
+  EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_one_id), IsNull());
+
+  ResourceName real_id_resource(
+      "com.app.test", ResourceType::kId, "two");
+  EXPECT_THAT(GetEntry(&new_table, real_id_resource, id_two_id), IsNull());
+  EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_two_id), NotNull());
+
+  ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id);
+  EXPECT_THAT(entry, NotNull());
+  ResourceConfigValue* config_value = entry->FindValue({});
+  Reference* ref = ValueCast<Reference>(config_value->value.get());
+  EXPECT_THAT(ref->id.value(), Eq(id_one_id));
+
+  // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
+  ResourceName obfuscated_integer_resource(
+      "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated");
+  entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id);
+  EXPECT_THAT(entry, NotNull());
+  config_value = entry->FindValue({});
+  BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+  EXPECT_THAT(bp->value.data, Eq(1u));
+
+  config_value = entry->FindValue(test::ParseConfigOrDie("v1"));
+  bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+  EXPECT_THAT(bp->value.data, Eq(2u));
+
+  entry = GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kString, "test"),
+                   string_test_id);
+  EXPECT_THAT(entry, NotNull());
+  config_value = entry->FindValue({});
+  String* s = ValueCast<String>(config_value->value.get());
+  EXPECT_THAT(*(s->value), Eq("foo"));
+}
+
 }  // namespace aapt