Add <staging-public-group-final> to aapt2

To allow apps that compiled against a pre-release SDK to continue
working for a period of time after API finalization, a new tag,
<staging-public-group-final>, has been added to aapt2.

When finalizing the framework resource API, converting
<staging-public-group> tags to <staging-public-group-final> will
cause aapt2 to generate the resource table so that there is a resource
entry for the old non-finalized (staged) resource ID and another entry
for the finalized resource ID of newly finalized resources. This allows
an application that compiled against the pre-release SDK to continue
resolving resources using pre-release resource IDs.

All references to pre-release resource IDs will be rewritten to their
finalized resource IDs through the information stored in the new staged
alias chunk. This allows applications compiled against
<staging-public-group> resources to use the newly finalized
resource ID without re-compilation.

When an application is re-compiled against the SDK with
<staging-public-group-final> tags, the application will use the
finalized resource IDs.

This change limits the use of the alias chunk to the framework for S.

Bug: 183411356
Test: aapt2_test
Change-Id: Iba1c3033c3c2f32de8e4a19b58d3921c971092c4
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 77c0872..ef3a62f 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -278,17 +278,19 @@
       printer->Println(StringPrintf(" entryCount=%zd", type.entries.size()));
 
       printer->Indent();
-      for (const ResourceEntry* entry : type.entries) {
+      for (const ResourceTableEntryView& entry : type.entries) {
         printer->Print("resource ");
-        printer->Print(entry->id.value_or_default(0).to_string());
+        printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0),
+                                  entry.id.value_or_default(0))
+                           .to_string());
         printer->Print(" ");
 
         // Write the name without the package (this is obvious and too verbose).
         printer->Print(to_string(type.type));
         printer->Print("/");
-        printer->Print(entry->name);
+        printer->Print(entry.name);
 
-        switch (entry->visibility.level) {
+        switch (entry.visibility.level) {
           case Visibility::Level::kPublic:
             printer->Print(" PUBLIC");
             break;
@@ -300,19 +302,24 @@
             break;
         }
 
-        if (entry->visibility.staged_api) {
+        if (entry.visibility.staged_api) {
           printer->Print(" STAGED");
         }
 
-        if (entry->overlayable_item) {
+        if (entry.overlayable_item) {
           printer->Print(" OVERLAYABLE");
         }
 
+        if (entry.staged_id) {
+          printer->Print(" STAGED_ID=");
+          printer->Print(entry.staged_id.value().id.to_string());
+        }
+
         printer->Println();
 
         if (options.show_values) {
           printer->Indent();
-          for (const auto& value : entry->values) {
+          for (const auto& value : entry.values) {
             printer->Print("(");
             printer->Print(value->config.to_string());
             printer->Print(") ");
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 1efabbb..f1e2da9 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -45,6 +45,7 @@
 namespace {
 constexpr const char* kPublicGroupTag = "public-group";
 constexpr const char* kStagingPublicGroupTag = "staging-public-group";
+constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final";
 }  // namespace
 
 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
@@ -109,6 +110,7 @@
   bool staged_api = false;
   bool allow_new = false;
   Maybe<OverlayableItem> overlayable_item;
+  Maybe<StagedId> staged_alias;
 
   std::string comment;
   std::unique_ptr<Value> value;
@@ -155,6 +157,10 @@
     res_builder.SetValue(std::move(res->value), res->config, res->product);
   }
 
+  if (res->staged_alias) {
+    res_builder.SetStagedId(res->staged_alias.value());
+  }
+
   bool error = false;
   if (!res->name.entry.empty()) {
     if (!table->AddResource(res_builder.Build(), diag)) {
@@ -532,6 +538,7 @@
       {"public", std::mem_fn(&ResourceParser::ParsePublic)},
       {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
       {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
+      {"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)},
       {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
       {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
                           std::placeholders::_2, std::placeholders::_3)},
@@ -671,7 +678,7 @@
     if (bag_iter != elToBagMap.end()) {
       // Ensure we have a name (unless this is a <public-group> or <overlayable>).
       if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
-          resource_type != "overlayable") {
+          resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") {
         if (!maybe_name) {
           diag_->Error(DiagMessage(out_resource->source)
                        << "<" << parser->element_name() << "> missing 'name' attribute");
@@ -1034,7 +1041,6 @@
       ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
           .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()},
           .source = item_source,
-          .id = next_id,
           .comment = std::move(comment),
       });
 
@@ -1060,6 +1066,14 @@
                         });
 }
 
+bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser,
+                                                  ParsedResource* out_resource) {
+  return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_,
+                        [](ParsedResource& parsed_entry, ResourceId id) {
+                          parsed_entry.staged_alias = StagedId{id, parsed_entry.source};
+                        });
+}
+
 bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   if (options_.visibility) {
     diag_->Error(DiagMessage(out_resource->source)
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 5c92def..2614997 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -115,6 +115,7 @@
   bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
+  bool ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 45ea654..8ab1493 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -83,6 +83,20 @@
   return action(found, iter);
 }
 
+struct ConfigKey {
+  const ConfigDescription* config;
+  const StringPiece& product;
+};
+
+template <typename T>
+bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) {
+  int cmp = lhs->config.compare(*rhs.config);
+  if (cmp == 0) {
+    cmp = StringPiece(lhs->product).compare(rhs.product);
+  }
+  return cmp < 0;
+}
+
 }  // namespace
 
 ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
@@ -134,23 +148,10 @@
   });
 }
 
-struct ConfigKey {
-  const ConfigDescription* config;
-  const StringPiece& product;
-};
-
-bool lt_config_key_ref(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
-  int cmp = lhs->config.compare(*rhs.config);
-  if (cmp == 0) {
-    cmp = StringPiece(lhs->product).compare(rhs.product);
-  }
-  return cmp < 0;
-}
-
 ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
                                               android::StringPiece product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref);
+                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -163,7 +164,7 @@
 const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config,
                                                     android::StringPiece product) const {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref);
+                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -176,7 +177,7 @@
 ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
                                                       const StringPiece& product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref);
+                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -296,6 +297,7 @@
   return CollisionResult::kConflict;
 }
 
+namespace {
 template <typename T, typename Comparer>
 struct SortedVectorInserter : public Comparer {
   std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el,
@@ -313,7 +315,7 @@
     if (found) {
       return &*it;
     }
-    return &*el.insert(it, std::move(value));
+    return &*el.insert(it, std::forward<T>(value));
   }
 };
 
@@ -331,35 +333,77 @@
 };
 
 struct EntryViewComparer {
-  bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) {
-    return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>(
-        *lhs, std::make_pair(rhs->name, rhs->id));
+  bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) {
+    return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>(
+        lhs, std::make_pair(rhs.name, rhs.id));
   }
 };
 
-ResourceTableView ResourceTable::GetPartitionedView() const {
-  ResourceTableView view;
+void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package,
+                              const ResourceTableType* type, const std::string& entry_name,
+                              const Maybe<ResourceId>& id, const Visibility& visibility,
+                              const Maybe<AllowNew>& allow_new,
+                              const Maybe<OverlayableItem>& overlayable_item,
+                              const Maybe<StagedId>& staged_id,
+                              const std::vector<std::unique_ptr<ResourceConfigValue>>& values) {
   SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
   SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
-  SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter;
+  SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter;
 
+  ResourceTablePackageView new_package{package->name,
+                                       id ? id.value().package_id() : Maybe<uint8_t>{}};
+  auto view_package = package_inserter.Insert(table.packages, std::move(new_package));
+
+  ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : Maybe<uint8_t>{}};
+  auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
+
+  if (visibility.level == Visibility::Level::kPublic) {
+    // Only mark the type visibility level as public, it doesn't care about being private.
+    view_type->visibility_level = Visibility::Level::kPublic;
+  }
+
+  ResourceTableEntryView new_entry{.name = entry_name,
+                                   .id = id ? id.value().entry_id() : Maybe<uint16_t>{},
+                                   .visibility = visibility,
+                                   .allow_new = allow_new,
+                                   .overlayable_item = overlayable_item,
+                                   .staged_id = staged_id};
+  for (auto& value : values) {
+    new_entry.values.emplace_back(value.get());
+  }
+
+  entry_inserter.Insert(view_type->entries, std::move(new_entry));
+}
+}  // namespace
+
+const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config,
+                                                             android::StringPiece product) const {
+  auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
+                               lt_config_key_ref<const ResourceConfigValue*>);
+  if (iter != values.end()) {
+    const ResourceConfigValue* value = *iter;
+    if (value->config == config && StringPiece(value->product) == product) {
+      return value;
+    }
+  }
+  return nullptr;
+}
+
+ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const {
+  ResourceTableView view;
   for (const auto& package : packages) {
     for (const auto& type : package->types) {
       for (const auto& entry : type->entries) {
-        ResourceTablePackageView new_package{
-            package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}};
-        auto view_package = package_inserter.Insert(view.packages, std::move(new_package));
+        InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id,
+                                 entry->visibility, entry->allow_new, entry->overlayable_item,
+                                 entry->staged_id, entry->values);
 
-        ResourceTableTypeView new_type{type->type,
-                                       entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}};
-        auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
-
-        if (entry->visibility.level == Visibility::Level::kPublic) {
-          // Only mark the type visibility level as public, it doesn't care about being private.
-          view_type->visibility_level = Visibility::Level::kPublic;
+        if (options.create_alias_entries && entry->staged_id) {
+          auto alias_id = entry->staged_id.value().id;
+          InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id,
+                                   entry->visibility, entry->allow_new, entry->overlayable_item, {},
+                                   entry->values);
         }
-
-        entry_inserter.Insert(view_type->entries, entry.get());
       }
     }
   }
@@ -368,6 +412,8 @@
   // for the same resource type within the same package. For this reason, if there are types with
   // multiple type ids, each type needs to exist in its own package in order to be queried by name.
   std::vector<ResourceTablePackageView> new_packages;
+  SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
+  SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
   for (auto& package : view.packages) {
     // If a new package was already created for a different type within this package, then
     // we can reuse those packages for other types that need to be extracted from this package.
@@ -498,6 +544,10 @@
     entry->allow_new = res.allow_new.value();
   }
 
+  if (res.staged_id.has_value()) {
+    entry->staged_id = res.staged_id.value();
+  }
+
   if (res.value != nullptr) {
     auto config_value = entry->FindOrCreateValue(res.config, res.product);
     if (!config_value->value) {
@@ -575,6 +625,28 @@
   return {};
 }
 
+bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) const {
+  ResourceTablePackage* package = FindPackage(name.package);
+  if (package == nullptr) {
+    return {};
+  }
+
+  ResourceTableType* type = package->FindType(name.type);
+  if (type == nullptr) {
+    return {};
+  }
+
+  auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry,
+                                   NameEqualRange<ResourceEntry>{});
+  for (auto it = entry_it.first; it != entry_it.second; ++it) {
+    if ((*it)->id == id) {
+      type->entries.erase(it);
+      return true;
+    }
+  }
+  return false;
+}
+
 std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
   std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>();
   CloningValueTransformer cloner(&new_table->string_pool);
@@ -640,6 +712,11 @@
   return *this;
 }
 
+NewResourceBuilder& NewResourceBuilder::SetStagedId(StagedId staged_alias) {
+  res_.staged_id = std::move(staged_alias);
+  return *this;
+}
+
 NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) {
   res_.allow_mangled = allow_mangled;
   return *this;
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 080ecc2..bae1d82 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -64,6 +64,12 @@
   std::string comment;
 };
 
+// Represents the staged resource id of a finalized resource.
+struct StagedId {
+  ResourceId id;
+  Source source;
+};
+
 struct Overlayable {
   Overlayable() = default;
    Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
@@ -124,6 +130,9 @@
   // The declarations of this resource as overlayable for RROs
   Maybe<OverlayableItem> overlayable_item;
 
+  // The staged resource id for a finalized resource.
+  Maybe<StagedId> staged_id;
+
   // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
@@ -194,14 +203,27 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
 };
 
+struct ResourceTableEntryView {
+  std::string name;
+  Maybe<uint16_t> id;
+  Visibility visibility;
+  Maybe<AllowNew> allow_new;
+  Maybe<OverlayableItem> overlayable_item;
+  Maybe<StagedId> staged_id;
+  std::vector<const ResourceConfigValue*> values;
+
+  const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
+                                       android::StringPiece product = {}) const;
+};
+
 struct ResourceTableTypeView {
   ResourceType type;
   Maybe<uint8_t> id;
   Visibility::Level visibility_level = Visibility::Level::kUndefined;
 
   // Entries sorted in ascending entry id order. If ids have not been assigned, the entries are
-  //  // sorted lexicographically.
-  std::vector<const ResourceEntry*> entries;
+  // sorted lexicographically.
+  std::vector<ResourceTableEntryView> entries;
 };
 
 struct ResourceTablePackageView {
@@ -212,6 +234,10 @@
   std::vector<ResourceTableTypeView> types;
 };
 
+struct ResourceTableViewOptions {
+  bool create_alias_entries = false;
+};
+
 struct ResourceTableView {
   // Packages sorted in ascending package id order. If ids have not been assigned, the packages are
   // sorted lexicographically.
@@ -237,6 +263,7 @@
   std::optional<Visibility> visibility;
   std::optional<OverlayableItem> overlayable;
   std::optional<AllowNew> allow_new;
+  std::optional<StagedId> staged_id;
   bool allow_mangled = false;
 };
 
@@ -249,6 +276,7 @@
   NewResourceBuilder& SetVisibility(Visibility id);
   NewResourceBuilder& SetOverlayable(OverlayableItem overlayable);
   NewResourceBuilder& SetAllowNew(AllowNew allow_new);
+  NewResourceBuilder& SetStagedId(StagedId id);
   NewResourceBuilder& SetAllowMangled(bool allow_mangled);
   NewResource Build();
 
@@ -273,7 +301,7 @@
 
   // Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id
   // order.
-  ResourceTableView GetPartitionedView() const;
+  ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;
 
   struct SearchResult {
     ResourceTablePackage* package;
@@ -283,6 +311,7 @@
 
   Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
   Maybe<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const;
+  bool RemoveResource(const ResourceNameRef& name, ResourceId id) const;
 
   // Returns the package struct with the given name, or nullptr if such a package does not
   // exist. The empty string is a valid package and typically is used to represent the
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index b45c040..95b7949 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -190,6 +190,12 @@
   uint32 overlayable_idx = 4;
 }
 
+// The staged resource ID definition of a finalized resource.
+message StagedId {
+  Source source = 1;
+  uint32 staged_id = 2;
+}
+
 // An entry ID in the range [0x0000, 0xffff].
 message EntryId {
   uint32 id = 1;
@@ -222,6 +228,9 @@
   // The set of values defined for this entry, each corresponding to a different
   // configuration/variant.
   repeated ConfigValue config_value = 6;
+
+  // The staged resource ID of this finalized resource.
+  StagedId staged_id = 7;
 }
 
 // A Configuration/Value pair.
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index df31087..3950f33 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -97,15 +97,15 @@
 
 static bool EmitResourceConfigValueDiff(
     IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
-    const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
+    const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
     const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
     const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
-    const ResourceEntry* entry_b, const ResourceConfigValue* config_value_b) {
+    const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) {
   Value* value_a = config_value_a->value.get();
   Value* value_b = config_value_b->value.get();
   if (!value_a->Equals(value_b)) {
     std::stringstream str_stream;
-    str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+    str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                << " config=" << config_value_a->config << " does not match:\n";
     value_a->Print(&str_stream);
     str_stream << "\n vs \n";
@@ -118,32 +118,32 @@
 
 static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
                                   const ResourceTablePackageView& pkg_a,
-                                  const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
-                                  LoadedApk* apk_b, const ResourceTablePackageView& pkg_b,
+                                  const ResourceTableTypeView& type_a,
+                                  const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
+                                  const ResourceTablePackageView& pkg_b,
                                   const ResourceTableTypeView& type_b,
-                                  const ResourceEntry* entry_b) {
+                                  const ResourceTableEntryView& entry_b) {
   bool diff = false;
-  for (const std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
-    auto config_value_b = entry_b->FindValue(config_value_a->config);
+  for (const ResourceConfigValue* config_value_a : entry_a.values) {
+    auto config_value_b = entry_b.FindValue(config_value_a->config);
     if (!config_value_b) {
       std::stringstream str_stream;
-      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                  << " config=" << config_value_a->config;
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
     } else {
-      diff |=
-          EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
-                                      apk_b, pkg_b, type_b, entry_b, config_value_b);
+      diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
+                                          apk_b, pkg_b, type_b, entry_b, config_value_b);
     }
   }
 
   // Check for any newly added config values.
-  for (const std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
-    auto config_value_a = entry_a->FindValue(config_value_b->config);
+  for (const ResourceConfigValue* config_value_b : entry_b.values) {
+    auto config_value_a = entry_a.FindValue(config_value_b->config);
     if (!config_value_a) {
       std::stringstream str_stream;
-      str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b->name
+      str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name
                  << " config=" << config_value_b->config;
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
@@ -164,36 +164,35 @@
     if (entry_b_iter == type_b.entries.end()) {
       // Type A contains a type that type B does not have.
       std::stringstream str_stream;
-      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << (*entry_a_iter)->name;
+      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name;
       EmitDiffLine(apk_a->GetSource(), str_stream.str());
       diff = true;
     } else if (entry_a_iter == type_a.entries.end()) {
       // Type B contains a type that type A does not have.
       std::stringstream str_stream;
-      str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/"
-                 << (*entry_b_iter)->name;
+      str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name;
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
     } else {
       const auto& entry_a = *entry_a_iter;
       const auto& entry_b = *entry_b_iter;
-      if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
+      if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
         std::stringstream str_stream;
-        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                    << " has different visibility (";
-        if (entry_b->visibility.staged_api) {
+        if (entry_b.visibility.staged_api) {
           str_stream << "STAGED ";
         }
-        if (entry_b->visibility.level == Visibility::Level::kPublic) {
+        if (entry_b.visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
         }
         str_stream << " vs ";
-        if (entry_a->visibility.staged_api) {
+        if (entry_a.visibility.staged_api) {
           str_stream << "STAGED ";
         }
-        if (entry_a->visibility.level == Visibility::Level::kPublic) {
+        if (entry_a.visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
@@ -201,19 +200,19 @@
         str_stream << ")";
         EmitDiffLine(apk_b->GetSource(), str_stream.str());
         diff = true;
-      } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
-                          entry_b->id)) {
+      } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
+                          entry_b.id)) {
         std::stringstream str_stream;
-        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                    << " has different public ID (";
-        if (entry_b->id) {
-          str_stream << "0x" << std::hex << entry_b->id.value();
+        if (entry_b.id) {
+          str_stream << "0x" << std::hex << entry_b.id.value();
         } else {
           str_stream << "none";
         }
         str_stream << " vs ";
-        if (entry_a->id) {
-          str_stream << "0x " << std::hex << entry_a->id.value();
+        if (entry_a.id) {
+          str_stream << "0x " << std::hex << entry_a.id.value();
         } else {
           str_stream << "none";
         }
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 3118eb8..430c184 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -402,8 +402,39 @@
   EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
 }
 
-TEST_F(LinkTest, StagedAndroidApi) {
-  StdErrDiagnostics diag;
+struct SourceXML {
+  std::string res_file_path;
+  std::string file_contents;
+};
+
+static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+                     LinkCommandBuilder&& link_args, CommandTestFixture* fixture,
+                     IDiagnostics* diag) {
+  TemporaryDir res_dir;
+  TemporaryDir compiled_res_dir;
+  for (auto& source_file : source_files) {
+    ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path,
+                                     source_file.file_contents, compiled_res_dir.path, diag));
+  }
+  ASSERT_TRUE(fixture->Link(
+      link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag));
+}
+
+static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+                     const std::string& java_root_path, CommandTestFixture* fixture,
+                     IDiagnostics* diag) {
+  auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build();
+
+  auto android_link_args = LinkCommandBuilder(fixture)
+                               .SetManifestFile(android_manifest)
+                               .AddParameter("--private-symbols", "com.android.internal")
+                               .AddParameter("--java", java_root_path);
+
+  BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag);
+}
+
+static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+                                 CommandTestFixture* fixture, IDiagnostics* diag) {
   const std::string android_values =
       R"(<resources>
           <public type="attr" name="finalized_res" id="0x01010001"/>
@@ -413,6 +444,10 @@
             <public name="staged_s_res" />
           </staging-public-group>
 
+          <staging-public-group type="string" first-id="0x01fd0080">
+            <public name="staged_s_string" />
+          </staging-public-group>
+
           <!-- SV2 staged attributes (support staged resources in a separate type id) -->
           <staging-public-group type="attr" first-id="0x01ff0049">
             <public name="staged_s2_res" />
@@ -423,46 +458,90 @@
             <public name="staged_t_res" />
           </staging-public-group>
 
-          <staging-public-group type="string" first-id="0x01fd0072">
-            <public name="staged_t_string" />
+          <attr name="finalized_res" />
+          <attr name="staged_s_res" />
+          <attr name="staged_s2_res" />
+          <attr name="staged_t_res" />
+          <string name="staged_s_string">Hello</string>
+         </resources>)";
+
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+  BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+                              CommandTestFixture* fixture, IDiagnostics* diag) {
+  const std::string android_values =
+      R"(<resources>
+          <public type="attr" name="finalized_res" id="0x01010001"/>
+          <public type="attr" name="staged_s_res" id="0x01010002"/>
+          <public type="attr" name="staged_s2_res" id="0x01010003"/>
+          <public type="string" name="staged_s_string" id="0x01020000"/>
+
+          <!-- S staged attributes (support staged resources in the same type id) -->
+          <staging-public-group-final type="attr" first-id="0x01010050">
+            <public name="staged_s_res" />
+          </staging-public-group-final>
+
+          <staging-public-group-final type="string" first-id="0x01fd0080">
+            <public name="staged_s_string" />
+          </staging-public-group-final>
+
+          <!-- SV2 staged attributes (support staged resources in a separate type id) -->
+          <staging-public-group-final type="attr" first-id="0x01ff0049">
+            <public name="staged_s2_res" />
+          </staging-public-group-final>
+
+          <!-- T staged attributes (support staged resources in multiple separate type ids) -->
+          <staging-public-group type="attr" first-id="0x01fe0063">
+            <public name="staged_t_res" />
           </staging-public-group>
 
           <attr name="finalized_res" />
           <attr name="staged_s_res" />
           <attr name="staged_s2_res" />
           <attr name="staged_t_res" />
-          <string name="staged_t_string">Hello</string>
+          <string name="staged_s_string">Hello</string>
          </resources>)";
 
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+  BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path,
+                               const std::string& sdk_path, CommandTestFixture* fixture,
+                               IDiagnostics* diag) {
   const std::string app_values =
       R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
            <attr name="bar" />
+           <style name="MyStyle">
+             <item name="android:staged_s_res">@android:string/staged_s_string</item>
+           </style>
            <declare-styleable name="ClientStyleable">
              <attr name="android:finalized_res" />
              <attr name="android:staged_s_res" />
              <attr name="bar" />
            </declare-styleable>
+           <public name="MyStyle" type="style" id="0x7f020000" />
          </resources>)";
 
-  const std::string android_res = GetTestPath("android-res");
-  ASSERT_TRUE(
-      CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag));
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values};
 
+  auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build();
+
+  auto app_link_args = LinkCommandBuilder(fixture)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("--java", java_path)
+                           .AddParameter("-I", sdk_path);
+
+  BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag);
+}
+
+TEST_F(LinkTest, StagedAndroidApi) {
+  StdErrDiagnostics diag;
   const std::string android_apk = GetTestPath("android.apk");
-  const std::string android_java = GetTestPath("android_java");
-  // clang-format off
-  auto android_manifest = ManifestBuilder(this)
-      .SetPackageName("android")
-      .Build();
-
-  auto android_link_args = LinkCommandBuilder(this)
-      .SetManifestFile(android_manifest)
-      .AddParameter("--private-symbols", "com.android.internal")
-      .AddParameter("--java", android_java)
-      .AddCompiledResDir(android_res, &diag)
-      .Build(android_apk);
-  // clang-format on
-  ASSERT_TRUE(Link(android_link_args, &diag));
+  const std::string android_java = GetTestPath("android-java");
+  BuildNonFinalizedSDK(android_apk, android_java, this, &diag);
 
   const std::string android_r_java = android_java + "/android/R.java";
   std::string android_r_contents;
@@ -473,33 +552,17 @@
       HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
   EXPECT_THAT(
       android_r_contents,
+      HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
+  EXPECT_THAT(
+      android_r_contents,
       HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }"));
   EXPECT_THAT(
       android_r_contents,
       HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
-  EXPECT_THAT(
-      android_r_contents,
-      HasSubstr("public static final int staged_t_string; static { staged_t_string=0x01fd0072; }"));
-
-  // Build an app that uses the framework attribute in a declare-styleable
-  const std::string client_res = GetTestPath("app-res");
-  ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag));
 
   const std::string app_apk = GetTestPath("app.apk");
-  const std::string app_java = GetTestPath("app_java");
-  // clang-format off
-  auto app_manifest = ManifestBuilder(this)
-      .SetPackageName("com.example.app")
-      .Build();
-
-  auto app_link_args = LinkCommandBuilder(this)
-      .SetManifestFile(app_manifest)
-      .AddParameter("--java", app_java)
-      .AddParameter("-I", android_apk)
-      .AddCompiledResDir(client_res, &diag)
-      .Build(app_apk);
-  // clang-format on
-  ASSERT_TRUE(Link(app_link_args, &diag));
+  const std::string app_java = GetTestPath("app-java");
+  BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag);
 
   const std::string client_r_java = app_java + "/com/example/app/R.java";
   std::string client_r_contents;
@@ -520,6 +583,10 @@
   ASSERT_TRUE(result.has_value());
   EXPECT_THAT(*result, Eq(0x01010050));
 
+  result = am.GetResourceId("android:string/staged_s_string");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01fd0080));
+
   result = am.GetResourceId("android:attr/staged_s2_res");
   ASSERT_TRUE(result.has_value());
   EXPECT_THAT(*result, Eq(0x01ff0049));
@@ -527,10 +594,88 @@
   result = am.GetResourceId("android:attr/staged_t_res");
   ASSERT_TRUE(result.has_value());
   EXPECT_THAT(*result, Eq(0x01fe0063));
+}
 
-  result = am.GetResourceId("android:string/staged_t_string");
+TEST_F(LinkTest, FinalizedAndroidApi) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildFinalizedSDK(android_apk, android_java, this, &diag);
+
+  const std::string android_r_java = android_java + "/android/R.java";
+  std::string android_r_contents;
+  ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;"));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;"));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;"));
+  EXPECT_THAT(
+      android_r_contents,
+      HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
+  ;
+
+  // Build an application against the non-finalized SDK and then load it into an AssetManager with
+  // the finalized SDK.
+  const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk");
+  const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java");
+  BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag);
+
+  const std::string app_apk = GetTestPath("app.apk");
+  const std::string app_java = GetTestPath("app-java");
+  BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag);
+
+  android::AssetManager2 am;
+  auto android_asset = android::ApkAssets::Load(android_apk);
+  auto app_against_non_final = android::ApkAssets::Load(app_apk);
+  ASSERT_THAT(android_asset, NotNull());
+  ASSERT_THAT(app_against_non_final, NotNull());
+  ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
+
+  auto result = am.GetResourceId("android:attr/finalized_res");
   ASSERT_TRUE(result.has_value());
-  EXPECT_THAT(*result, Eq(0x01fd0072));
+  EXPECT_THAT(*result, Eq(0x01010001));
+
+  result = am.GetResourceId("android:attr/staged_s_res");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01010002));
+
+  result = am.GetResourceId("android:string/staged_s_string");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01020000));
+
+  result = am.GetResourceId("android:attr/staged_s2_res");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01010003));
+
+  {
+    auto style = am.GetBag(0x7f020000);
+    ASSERT_TRUE(style.has_value());
+
+    auto& entry = (*style)->entries[0];
+    EXPECT_THAT(entry.key, Eq(0x01010002));
+    EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+    EXPECT_THAT(entry.value.data, Eq(0x01020000));
+  }
+
+  // Re-compile the application against the finalized SDK and then load it into an AssetManager with
+  // the finalized SDK.
+  const std::string app_apk_respin = GetTestPath("app-respin.apk");
+  const std::string app_java_respin = GetTestPath("app-respin-java");
+  BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag);
+
+  auto app_against_final = android::ApkAssets::Load(app_apk_respin);
+  ASSERT_THAT(app_against_final, NotNull());
+  ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
+
+  {
+    auto style = am.GetBag(0x7f020000);
+    ASSERT_TRUE(style.has_value());
+
+    auto& entry = (*style)->entries[0];
+    EXPECT_THAT(entry.key, Eq(0x01010002));
+    EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+    EXPECT_THAT(entry.value.data, Eq(0x01020000));
+  }
 }
 
 TEST_F(LinkTest, MacroSubstitution) {
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 9a50b26..339b8af 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -129,11 +129,16 @@
     for (auto& type : package->types) {
       for (auto& entry : type->entries) {
         const ResourceName name(package->name, type->type, entry->name);
-        if (entry->id) {
-          if (!assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
-                                      context->GetDiagnostics())) {
-            return false;
-          }
+        if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
+                                                 context->GetDiagnostics())) {
+          return false;
+        }
+
+        auto v = entry->visibility;
+        v.staged_api = true;
+        if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v,
+                                                        context->GetDiagnostics())) {
+          return false;
         }
 
         if (assigned_id_map_) {
@@ -237,7 +242,7 @@
   if (type_id_ != id.type_id()) {
     // Currently there cannot be multiple type ids for a single type.
     std::stringstream error;
-    error << "type '" << name.type << "' already has ID " << std::hex << (int)id.type_id();
+    error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_;
     return unexpected(error.str());
   }
 
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index f1b350f..2ec01cd 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -254,6 +254,12 @@
         }
         break;
 
+      case android::RES_TABLE_STAGED_ALIAS_TYPE:
+        if (!ParseOverlayable(parser.chunk())) {
+          return false;
+        }
+        break;
+
       default:
         diag_->Warn(DiagMessage(source_)
                     << "unexpected chunk type "
@@ -489,6 +495,52 @@
   return true;
 }
 
+bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) {
+  auto header = ConvertTo<ResTable_staged_alias_header>(chunk);
+  if (!header) {
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk");
+    return false;
+  }
+
+  const auto ref_begin = reinterpret_cast<const ResTable_staged_alias_entry*>(
+      ((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize));
+  const auto ref_end = ref_begin + util::DeviceToHost32(header->count);
+  for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) {
+    const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId));
+    const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId));
+
+    // If the staged alias chunk comes before the type chunks, the resource ids and resource name
+    // pairing will not exist at this point.
+    const auto iter = id_index_.find(finalized_id);
+    if (iter == id_index_.cend()) {
+      diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized"
+                                        << " resource ID " << finalized_id);
+      return false;
+    }
+
+    // Set the staged if of the finalized resource.
+    const auto& resource_name = iter->second;
+    const StagedId staged_id_def{.id = staged_id};
+    if (!table_->AddResource(NewResourceBuilder(resource_name)
+                                 .SetId(finalized_id, OnIdConflict::CREATE_ENTRY)
+                                 .SetStagedId(staged_id_def)
+                                 .SetAllowMangled(true)
+                                 .Build(),
+                             diag_)) {
+      return false;
+    }
+
+    // Since a the finalized resource entry is cloned and added to the resource table under the
+    // staged resource id, remove the cloned resource entry from the table.
+    if (!table_->RemoveResource(resource_name, staged_id)) {
+      diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged "
+                                        << " resource ID " << staged_id);
+      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 13dd982..cd71d16 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -57,6 +57,7 @@
                  uint8_t package_id);
   bool ParseLibrary(const android::ResChunk_header* chunk);
   bool ParseOverlayable(const android::ResChunk_header* chunk);
+  bool ParseStagedAliases(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 74ecf47..a9192e8 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -72,7 +72,7 @@
 }
 
 struct FlatEntry {
-  const ResourceEntry* entry;
+  const ResourceTableEntryView* entry;
   const Value* value;
 
   // The entry string pool index to the entry's name.
@@ -286,6 +286,10 @@
       return false;
     }
 
+    if (!FlattenAliases(buffer)) {
+      return false;
+    }
+
     pkg_writer.Finish();
     return true;
   }
@@ -351,8 +355,8 @@
 
     BigBuffer values_buffer(512);
     for (FlatEntry& flat_entry : *entries) {
-      CHECK(static_cast<size_t>(flat_entry.entry->id.value().entry_id()) < num_total_entries);
-      offsets[flat_entry.entry->id.value().entry_id()] = values_buffer.size();
+      CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
+      offsets[flat_entry.entry->id.value()] = values_buffer.size();
       if (!FlattenValue(&flat_entry, &values_buffer)) {
         diag_->Error(DiagMessage()
                      << "failed to flatten resource '"
@@ -404,6 +408,26 @@
     return true;
   }
 
+  bool FlattenAliases(BigBuffer* buffer) {
+    if (aliases_.empty()) {
+      return true;
+    }
+
+    ChunkWriter alias_writer(buffer);
+    auto header =
+        alias_writer.StartChunk<ResTable_staged_alias_header>(RES_TABLE_STAGED_ALIAS_TYPE);
+    header->count = util::HostToDevice32(aliases_.size());
+
+    auto mapping = alias_writer.NextBlock<ResTable_staged_alias_entry>(aliases_.size());
+    for (auto& p : aliases_) {
+      mapping->stagedResId = util::HostToDevice32(p.first);
+      mapping->finalizedResId = util::HostToDevice32(p.second);
+      ++mapping;
+    }
+    alias_writer.Finish();
+    return true;
+  }
+
   bool FlattenOverlayable(BigBuffer* buffer) {
     std::set<ResourceId> seen_ids;
     std::map<std::string, OverlayableChunk> overlayable_chunks;
@@ -413,18 +437,17 @@
       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>";
-        if (!entry->overlayable_item) {
+        if (!entry.overlayable_item) {
           continue;
         }
 
-        const OverlayableItem& item = entry->overlayable_item.value();
+        const OverlayableItem& item = entry.overlayable_item.value();
 
         // Resource ids should only appear once in the resource table
-        ResourceId id =
-            android::make_resid(package_.id.value(), type.id.value(), entry->id.value().entry_id());
+        ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value());
         CHECK(seen_ids.find(id) == seen_ids.end())
             << "multiple overlayable definitions found for resource "
-            << ResourceName(package_.name, type.type, entry->name).to_string();
+            << ResourceName(package_.name, type.type, entry.name).to_string();
         seen_ids.insert(id);
 
         // Find the overlayable chunk with the specified name
@@ -452,9 +475,8 @@
 
         if (item.policies == 0) {
           context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
-                                                << "overlayable "
-                                                << entry->name
-                                                << " does not specify policy");
+                                            << "overlayable " << entry.name
+                                            << " does not specify policy");
           return false;
         }
 
@@ -520,7 +542,8 @@
   }
 
   bool FlattenTypeSpec(const ResourceTableTypeView& type,
-                       const std::vector<const ResourceEntry*>& sorted_entries, BigBuffer* buffer) {
+                       const std::vector<ResourceTableEntryView>& sorted_entries,
+                       BigBuffer* buffer) {
     ChunkWriter type_spec_writer(buffer);
     ResTable_typeSpec* spec_header =
         type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@@ -534,7 +557,7 @@
     // We can't just take the size of the vector. There may be holes in the
     // entry ID space.
     // Since the entries are sorted by ID, the last one will be the biggest.
-    const size_t num_entries = sorted_entries.back()->id.value().entry_id() + 1;
+    const size_t num_entries = sorted_entries.back().id.value() + 1;
 
     spec_header->entryCount = util::HostToDevice32(num_entries);
 
@@ -542,23 +565,23 @@
     // show for which configuration axis the resource changes.
     uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
 
-    for (const ResourceEntry* entry : sorted_entries) {
-      const uint16_t entry_id = entry->id.value().entry_id();
+    for (const ResourceTableEntryView& entry : sorted_entries) {
+      const uint16_t entry_id = entry.id.value();
 
       // Populate the config masks for this entry.
       uint32_t& entry_config_masks = config_masks[entry_id];
-      if (entry->visibility.level == Visibility::Level::kPublic) {
+      if (entry.visibility.level == Visibility::Level::kPublic) {
         entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
       }
-      if (entry->visibility.staged_api) {
+      if (entry.visibility.staged_api) {
         entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API);
       }
 
-      const size_t config_count = entry->values.size();
+      const size_t config_count = entry.values.size();
       for (size_t i = 0; i < config_count; i++) {
-        const ConfigDescription& config = entry->values[i]->config;
+        const ConfigDescription& config = entry.values[i]->config;
         for (size_t j = i + 1; j < config_count; j++) {
-          config_masks[entry_id] |= util::HostToDevice32(config.diff(entry->values[j]->config));
+          config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config));
         }
       }
     }
@@ -590,7 +613,7 @@
       }
 
       // Since the entries are sorted by ID, the last ID will be the largest.
-      const size_t num_entries = type.entries.back()->id.value().entry_id() + 1;
+      const size_t num_entries = type.entries.back().id.value() + 1;
 
       // The binary resource table lists resource entries for each
       // configuration.
@@ -603,20 +626,26 @@
       // hardcoded string uses characters which make it an invalid resource name
       const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
 
-      for (const ResourceEntry* entry : type.entries) {
+      for (const ResourceTableEntryView& entry : type.entries) {
+        if (entry.staged_id) {
+          aliases_.insert(std::make_pair(
+              entry.staged_id.value().id.id,
+              ResourceId(package_.id.value(), type.id.value(), entry.id.value()).id));
+        }
+
         uint32_t local_key_index;
-        ResourceName resource_name({}, type.type, entry->name);
+        ResourceName resource_name({}, type.type, entry.name);
         if (!collapse_key_stringpool_ ||
             name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
-          local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
+          local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
         } else {
           // resource isn't exempt from collapse, add it as obfuscated value
           local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
         }
         // Group values by configuration.
-        for (auto& config_value : entry->values) {
+        for (auto& config_value : entry.values) {
           config_to_entry_list_map[config_value->config].push_back(
-              FlatEntry{entry, config_value->value.get(), local_key_index});
+              FlatEntry{&entry, config_value->value.get(), local_key_index});
         }
       }
 
@@ -667,6 +696,7 @@
   StringPool key_pool_;
   bool collapse_key_stringpool_;
   const std::set<ResourceName>& name_collapse_exemptions_;
+  std::map<uint32_t, uint32_t> aliases_;
 };
 
 }  // namespace
@@ -684,7 +714,8 @@
   });
 
   // Write the ResTable header.
-  const auto& table_view = table->GetPartitionedView();
+  const auto& table_view =
+      table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true});
   ChunkWriter table_writer(buffer_);
   ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
   table_header->packageCount = util::HostToDevice32(table_view.packages.size());
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index ec331df..236c381 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -498,10 +498,20 @@
                                               out_error)) {
           return false;
         }
-
         entry->overlayable_item = std::move(overlayable_item);
       }
 
+      if (pb_entry.has_staged_id()) {
+        const pb::StagedId& pb_staged_id = pb_entry.staged_id();
+
+        StagedId staged_id;
+        if (pb_staged_id.has_source()) {
+          DeserializeSourceFromPb(pb_staged_id.source(), src_pool, &staged_id.source);
+        }
+        staged_id.id = pb_staged_id.staged_id();
+        entry->staged_id = std::move(staged_id);
+      }
+
       ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
                        pb_entry.entry_id().id());
       if (resid.is_valid()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index d2f0336..6042ba8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -364,43 +364,52 @@
       static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
       for (const auto& entry : type.entries) {
         pb::Entry* pb_entry = pb_type->add_entry();
-        if (entry->id) {
-          pb_entry->mutable_entry_id()->set_id(entry->id.value().entry_id());
+        if (entry.id) {
+          pb_entry->mutable_entry_id()->set_id(entry.id.value());
         }
-        ResourceName resource_name({}, type.type, 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);
+          pb_entry->set_name(entry.name);
         }
 
         // Write the Visibility struct.
         pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
-        pb_visibility->set_staged_api(entry->visibility.staged_api);
-        pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
+        pb_visibility->set_staged_api(entry.visibility.staged_api);
+        pb_visibility->set_level(SerializeVisibilityToPb(entry.visibility.level));
         if (source_pool != nullptr) {
-          SerializeSourceToPb(entry->visibility.source, source_pool.get(),
+          SerializeSourceToPb(entry.visibility.source, source_pool.get(),
                               pb_visibility->mutable_source());
         }
-        pb_visibility->set_comment(entry->visibility.comment);
+        pb_visibility->set_comment(entry.visibility.comment);
 
-        if (entry->allow_new) {
+        if (entry.allow_new) {
           pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
           if (source_pool != nullptr) {
-            SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(),
+            SerializeSourceToPb(entry.allow_new.value().source, source_pool.get(),
                                 pb_allow_new->mutable_source());
           }
-          pb_allow_new->set_comment(entry->allow_new.value().comment);
+          pb_allow_new->set_comment(entry.allow_new.value().comment);
         }
 
-        if (entry->overlayable_item) {
-          SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables,
+        if (entry.overlayable_item) {
+          SerializeOverlayableItemToPb(entry.overlayable_item.value(), overlayables,
                                        source_pool.get(), pb_entry, out_table);
         }
 
-        for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
+        if (entry.staged_id) {
+          pb::StagedId* pb_staged_id = pb_entry->mutable_staged_id();
+          if (source_pool != nullptr) {
+            SerializeSourceToPb(entry.staged_id.value().source, source_pool.get(),
+                                pb_staged_id->mutable_source());
+          }
+          pb_staged_id->set_staged_id(entry.staged_id.value().id.id);
+        }
+
+        for (const ResourceConfigValue* config_value : entry.values) {
           pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
           SerializeConfig(config_value->config, pb_config_value->mutable_config());
           pb_config_value->mutable_config()->set_product(config_value->product);
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index e563eda..38c811f 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -928,4 +928,27 @@
   EXPECT_THAT(deserialized->alias_namespaces, Eq(original->alias_namespaces));
 }
 
+TEST(ProtoSerializeTest, StagedId) {
+  CloningValueTransformer cloner(nullptr);
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                                             .Add(NewResourceBuilder("com.app.a:string/foo")
+                                                      .SetStagedId(StagedId{.id = 0x01ff0001})
+                                                      .Build())
+                                             .Build();
+
+  ResourceTable new_table;
+  pb::ResourceTable pb_table;
+  MockFileCollection files;
+  std::string error;
+  SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+  EXPECT_THAT(error, IsEmpty());
+
+  auto result = new_table.FindResource(test::ParseNameOrDie("com.app.a:string/foo"));
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->staged_id);
+  EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001)));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index bc93ec6..22f4d18 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -151,6 +151,18 @@
     dst_entry->overlayable_item = std::move(src_entry->overlayable_item);
   }
 
+  if (src_entry->staged_id) {
+    if (dst_entry->staged_id &&
+        dst_entry->staged_id.value().id != src_entry->staged_id.value().id) {
+      context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source)
+                                       << "conflicting staged id declaration for resource '"
+                                       << src_entry->name << "'");
+      context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source)
+                                       << "previous declaration here");
+    }
+    dst_entry->staged_id = std::move(src_entry->staged_id);
+  }
+
   return true;
 }
 
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index f94f0fe..285e5a1 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -80,9 +80,6 @@
 }
 
 void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) {
-  CHECK(util::StartsWith(path, temp_dir_))
-      << "Attempting to create a file outside of test temporary directory.";
-
   // Create any intermediate directories specified in the path
   auto pos = std::find(path.rbegin(), path.rend(), file::sDirSep);
   if (pos != path.rend()) {