Encoding of <overlayable> and <policy>

This change defines two new chunks for encoding overlayable information.
RES_TABLE_OVERLAYABLE_POLICY_TYPE contains flags that represent
restrictions enforced on overlays that try to overlay a specific set of
resource ids. The chunk header is followed by ResTable_ref for each id
that belongs to the policy type. A policy chunk will be created for
every unique combination of policies that are defined in overlayable
declarations.

RES_TABLE_OVERLAYABLE_TYPE holds policy blocks. Since <overlayable>
does not currently have any attributes, only one overlayable block is
encoded in an APK.

This change also removes the SPEC_OVERLAYABLE flag because the runtime
does not use the flag, and the overlayable chunk encoding renders it
obsolete.

Bug: 110869880
Bug: 117545186
Test: libandroidfw_tests and aapt2_tests
Change-Id: I45ae9bf4176699f14c85e2b7a2e8560185d8a0b8
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index ed70fb3..df0daeb 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -240,6 +240,12 @@
         }
         break;
 
+      case android::RES_TABLE_OVERLAYABLE_TYPE:
+        if (!ParseOverlayable(parser.chunk())) {
+          return false;
+        }
+        break;
+
       default:
         diag_->Warn(DiagMessage(source_)
                     << "unexpected chunk type "
@@ -383,24 +389,12 @@
       return false;
     }
 
-    const uint32_t type_spec_flags = entry_type_spec_flags_[res_id];
-    if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0 ||
-        (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) != 0) {
-      if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
-        Visibility visibility;
-        visibility.level = Visibility::Level::kPublic;
-        visibility.source = source_.WithLine(0);
-        if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) {
-          return false;
-        }
-      }
-
-      if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) {
-        Overlayable overlayable;
-        overlayable.source = source_.WithLine(0);
-        if (!table_->AddOverlayableMangled(name, overlayable, diag_)) {
-          return false;
-        }
+    if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+      Visibility visibility;
+      visibility.level = Visibility::Level::kPublic;
+      visibility.source = source_.WithLine(0);
+      if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) {
+        return false;
       }
 
       // Erase the ID from the map once processed, so that we don't mark the same symbol more than
@@ -433,6 +427,72 @@
   return true;
 }
 
+bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
+  const ResTable_overlayable_header* header = ConvertTo<ResTable_overlayable_header>(chunk);
+  if (!header) {
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_category_header chunk");
+    return false;
+  }
+
+  ResChunkPullParser parser(GetChunkData(chunk),
+                            GetChunkDataLen(chunk));
+  while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+    if (util::DeviceToHost16(parser.chunk()->type) == android::RES_TABLE_OVERLAYABLE_POLICY_TYPE) {
+      const ResTable_overlayable_policy_header* policy_header =
+          ConvertTo<ResTable_overlayable_policy_header>(parser.chunk());
+
+      std::vector<Overlayable::Policy> policies;
+      if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) {
+        policies.push_back(Overlayable::Policy::kPublic);
+      }
+      if (policy_header->policy_flags
+          & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) {
+        policies.push_back(Overlayable::Policy::kSystem);
+      }
+      if (policy_header->policy_flags
+          & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) {
+        policies.push_back(Overlayable::Policy::kVendor);
+      }
+      if (policy_header->policy_flags
+          & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) {
+        policies.push_back(Overlayable::Policy::kProduct);
+      }
+      if (policy_header->policy_flags
+          & ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION) {
+        policies.push_back(Overlayable::Policy::kProductServices);
+      }
+
+      const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>(
+          ((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize));
+      const ResTable_ref* const ref_end = ref_begin
+          + util::DeviceToHost32(policy_header->entry_count);
+      for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) {
+        ResourceId res_id(util::DeviceToHost32(ref_iter->ident));
+        const auto iter = id_index_.find(res_id);
+
+        // If the overlayable chunk comes before the type chunks, the resource ids and resource name
+        // pairing will not exist at this point.
+        if (iter == id_index_.cend()) {
+          diag_->Error(DiagMessage(source_) << "failed to find resource name for overlayable"
+                                            << " resource " << res_id);
+          return false;
+        }
+
+        for (Overlayable::Policy policy : policies) {
+          Overlayable overlayable;
+          overlayable.source = source_.WithLine(0);
+          overlayable.policy = policy;
+          if (!table_->AddOverlayable(iter->second, overlayable, diag_)) {
+            return false;
+          }
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
 std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
                                                        const ConfigDescription& config,
                                                        const android::Res_value& value) {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index 2bdc051..a2eee50 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -54,6 +54,7 @@
   bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
   bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
   bool ParseLibrary(const android::ResChunk_header* chunk);
+  bool ParseOverlayable(const android::ResChunk_header* chunk);
 
   std::unique_ptr<Item> ParseValue(const ResourceNameRef& name,
                                    const android::ConfigDescription& config,
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 6c1a9ba..976c328 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -24,6 +24,7 @@
 #include "android-base/logging.h"
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
+#include "androidfw/ResourceUtils.h"
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
@@ -216,6 +217,11 @@
   size_t entry_count_ = 0;
 };
 
+struct PolicyChunk {
+  uint32_t policy_flags;
+  std::set<ResourceId> ids;
+};
+
 class PackageFlattener {
  public:
   PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
@@ -267,6 +273,8 @@
       FlattenLibrarySpec(buffer);
     }
 
+    FlattenOverlayable(buffer);
+
     pkg_writer.Finish();
     return true;
   }
@@ -413,6 +421,97 @@
     return sorted_entries;
   }
 
+  void FlattenOverlayable(BigBuffer* buffer) {
+    std::vector<PolicyChunk> policies;
+
+    CHECK(bool(package_->id)) << "package must have an ID set when flattening <overlayable>";
+    for (auto& type : package_->types) {
+      CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>";
+      for (auto& entry : type->entries) {
+        CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>";
+
+        // TODO(b/120298168): Convert the policies vector to a policy set or bitmask
+        if (!entry->overlayable_declarations.empty()) {
+          uint16_t policy_flags = 0;
+          for (Overlayable overlayable : entry->overlayable_declarations) {
+            if (overlayable.policy) {
+              switch (overlayable.policy.value()) {
+                case Overlayable::Policy::kPublic:
+                  policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
+                  break;
+                case Overlayable::Policy::kSystem:
+                  policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
+                  break;
+                case Overlayable::Policy::kVendor:
+                  policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
+                  break;
+                case Overlayable::Policy::kProduct:
+                  policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
+                  break;
+                case Overlayable::Policy::kProductServices:
+                  policy_flags |=
+                      ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION;
+                  break;
+              }
+            } else {
+              // Encode overlayable entries defined without a policy as publicly overlayable
+              policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
+            }
+          }
+
+          // Find the overlayable policy chunk with the same policies as the entry
+          PolicyChunk* policy_chunk = nullptr;
+          for (PolicyChunk& policy : policies) {
+            if (policy.policy_flags == policy_flags) {
+              policy_chunk = &policy;
+              break;
+            }
+          }
+
+          // Create a new policy chunk if an existing one with the same policy cannot be found
+          if (policy_chunk == nullptr) {
+            PolicyChunk p;
+            p.policy_flags = policy_flags;
+            policies.push_back(p);
+            policy_chunk = &policies.back();
+          }
+
+          policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(),
+                                                       entry->id.value()));
+        }
+      }
+    }
+
+    if (policies.empty()) {
+      // Only write the overlayable chunk if the APK has overlayable entries
+      return;
+    }
+
+    ChunkWriter writer(buffer);
+    writer.StartChunk<ResTable_overlayable_header>(RES_TABLE_OVERLAYABLE_TYPE);
+
+    // Write each policy block for the overlayable
+    for (PolicyChunk& policy : policies) {
+      ChunkWriter policy_writer(buffer);
+      ResTable_overlayable_policy_header* policy_type =
+          policy_writer.StartChunk<ResTable_overlayable_policy_header>(
+              RES_TABLE_OVERLAYABLE_POLICY_TYPE);
+      policy_type->policy_flags = util::HostToDevice32(policy.policy_flags);
+      policy_type->entry_count = util::HostToDevice32(static_cast<uint32_t>(policy.ids.size()));
+
+      // Write the ids after the policy header
+      ResTable_ref* id_block = policy_writer.NextBlock<ResTable_ref>(policy.ids.size());
+      for (const ResourceId& id : policy.ids) {
+        id_block->ident = util::HostToDevice32(id.id);
+        id_block++;
+      }
+
+      policy_writer.Finish();
+    }
+
+    writer.Finish();
+  }
+
   bool FlattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sorted_entries,
                        BigBuffer* buffer) {
     ChunkWriter type_spec_writer(buffer);
@@ -446,11 +545,6 @@
         config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
       }
 
-      if (!entry->overlayable_declarations.empty()) {
-        config_masks[entry->id.value()] |=
-            util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE);
-      }
-
       const size_t config_count = entry->values.size();
       for (size_t i = 0; i < config_count; i++) {
         const ConfigDescription& config = entry->values[i]->config;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index cd1414c7e..410efbe 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -628,24 +628,108 @@
 }
 
 TEST_F(TableFlattenerTest, FlattenOverlayable) {
+  std::string name = "com.app.test:integer/overlayable";
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.test", 0x7f)
-          .AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000))
+          .AddSimple(name, ResourceId(0x7f020000))
+          .AddOverlayable(name, Overlayable::Policy::kProduct)
+          .AddOverlayable(name, Overlayable::Policy::kSystem)
+          .AddOverlayable(name, Overlayable::Policy::kVendor)
           .Build();
 
-  ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
-                                    Overlayable{}, test::GetDiagnostics()));
+  ResourceTable output_table;
+  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
 
-  ResTable res_table;
-  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
-
-  const StringPiece16 overlayable_name(u"com.app.test:integer/overlayable");
-  uint32_t spec_flags = 0u;
-  ASSERT_THAT(res_table.identifierForName(overlayable_name.data(), overlayable_name.size(), nullptr,
-                                          0u, nullptr, 0u, &spec_flags),
-              Gt(0u));
-  EXPECT_TRUE(spec_flags & android::ResTable_typeSpec::SPEC_OVERLAYABLE);
+  auto search_result = output_table.FindResource(test::ParseNameOrDie(name));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
+            Overlayable::Policy::kSystem);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
+            Overlayable::Policy::kVendor);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
+            Overlayable::Policy::kProduct);
 }
 
+TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
+  std::string name_zero = "com.app.test:integer/overlayable_zero";
+  std::string name_one = "com.app.test:integer/overlayable_one";
+  std::string name_two = "com.app.test:integer/overlayable_two";
+  std::string name_three = "com.app.test:integer/overlayable_three";
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.test", 0x7f)
+          .AddSimple(name_zero, ResourceId(0x7f020000))
+          .AddOverlayable(name_zero, Overlayable::Policy::kProduct)
+          .AddOverlayable(name_zero, Overlayable::Policy::kSystem)
+          .AddOverlayable(name_zero, Overlayable::Policy::kProductServices)
+          .AddSimple(name_one, ResourceId(0x7f020001))
+          .AddOverlayable(name_one, Overlayable::Policy::kPublic)
+          .AddOverlayable(name_one, Overlayable::Policy::kSystem)
+          .AddSimple(name_two, ResourceId(0x7f020002))
+          .AddOverlayable(name_two, Overlayable::Policy::kProduct)
+          .AddOverlayable(name_two, Overlayable::Policy::kSystem)
+          .AddOverlayable(name_two, Overlayable::Policy::kProductServices)
+          .AddSimple(name_three, ResourceId(0x7f020003))
+          .AddOverlayable(name_three, {})
+          .Build();
+
+  ResourceTable output_table;
+  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
+
+  auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
+            Overlayable::Policy::kSystem);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
+            Overlayable::Policy::kProduct);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
+            Overlayable::Policy::kProductServices);
+
+  search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 2);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
+            Overlayable::Policy::kPublic);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
+            Overlayable::Policy::kSystem);
+
+  search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
+            Overlayable::Policy::kSystem);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
+            Overlayable::Policy::kProduct);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
+            Overlayable::Policy::kProductServices);
+
+  search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 1);
+  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
+  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
+            Overlayable::Policy::kPublic);
+
+}
+
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 3a5d585..1b6626a 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -368,7 +368,16 @@
         // Symbol state information may be lost if there is no value for the resource.
         if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) {
           context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source)
-                                           << "no definition for declared symbol '" << name << "'");
+                                               << "no definition for declared symbol '" << name
+                                               << "'");
+          error = true;
+        }
+
+        // Ensure that definitions for values declared as overlayable exist
+        if (!entry->overlayable_declarations.empty() && entry->values.empty()) {
+          context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_declarations[0].source)
+                                           << "no definition for overlayable symbol '"
+                                           << name << "'");
           error = true;
         }