AAPT2: Shared library support

Test: make aapt2_tests
Change-Id: I98dddf1367e6c0ac425bb20be46e6ff05f4f2f45
diff --git a/tests/SharedLibrary/client/Android.mk b/tests/SharedLibrary/client/Android.mk
index 1d66bb9..a04fb05 100644
--- a/tests/SharedLibrary/client/Android.mk
+++ b/tests/SharedLibrary/client/Android.mk
@@ -1,6 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_USE_AAPT2 := true
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
 LOCAL_RES_LIBRARIES := SharedLibrary
diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk
index b2fc369..78fcb8b 100644
--- a/tests/SharedLibrary/lib/Android.mk
+++ b/tests/SharedLibrary/lib/Android.mk
@@ -1,5 +1,6 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/tests/SharedLibrary/lib/res/values/public.xml b/tests/SharedLibrary/lib/res/values/public.xml
index 23d307e..8d7a7ad 100644
--- a/tests/SharedLibrary/lib/res/values/public.xml
+++ b/tests/SharedLibrary/lib/res/values/public.xml
@@ -15,5 +15,5 @@
 
     <public type="drawable" name="size_48x48" id="0x00030000" />
 
-    <public type="array" name="animals"     id="0x02050000" />
+    <public type="array" name="animals"     id="0x00050000" />
 </resources>
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index ba378d7..7d68d36 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
 static const char* sMajorVersion = "2";
 
 // Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "7";
+static const char* sMinorVersion = "8";
 
 int PrintVersion() {
   std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index dba2d09..1305a4c 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -62,6 +62,8 @@
     return policy_.packages_to_mangle.count(package) != 0;
   }
 
+  const std::string& GetTargetPackageName() const { return policy_.target_package_name; }
+
   /**
    * Returns a mangled name that is a combination of `name` and `package`.
    * The mangled name should contain symbols that are illegal to define in XML,
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 4d915d9..cffe8d5 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -138,6 +138,10 @@
   ResourceId(uint8_t p, uint8_t t, uint16_t e);
 
   bool is_valid() const;
+
+  // Returns true if the ID is a valid ID or dynamic ID (package ID can be 0).
+  bool is_valid_dynamic() const;
+
   uint8_t package_id() const;
   uint8_t type_id() const;
   uint16_t entry_id() const;
@@ -211,6 +215,8 @@
   return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
 }
 
+inline bool ResourceId::is_valid_dynamic() const { return (id & 0x00ff0000u) != 0; }
+
 inline uint8_t ResourceId::package_id() const {
   return static_cast<uint8_t>(id >> 24);
 }
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 47ca266..8461905 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -701,14 +701,11 @@
 
   out_resource->name.type = *parsed_type;
 
-  if (Maybe<StringPiece> maybe_id_str =
-          xml::FindNonEmptyAttribute(parser, "id")) {
-    Maybe<ResourceId> maybe_id =
-        ResourceUtils::ParseResourceId(maybe_id_str.value());
+  if (Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "id")) {
+    Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
     if (!maybe_id) {
-      diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
-                                                     << maybe_id.value()
-                                                     << "' in <public>");
+      diag_->Error(DiagMessage(out_resource->source)
+                   << "invalid resource ID '" << maybe_id_str.value() << "' in <public>");
       return false;
     }
     out_resource->id = maybe_id.value();
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index dd78750..6e4b450 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -387,8 +387,7 @@
   }
 
   ResourceTablePackage* package = FindOrCreatePackage(name.package);
-  if (res_id.is_valid() && package->id &&
-      package->id.value() != res_id.package_id()) {
+  if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
     diag->Error(DiagMessage(value->GetSource())
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but package '" << package->name << "' already has ID "
@@ -397,7 +396,7 @@
   }
 
   ResourceTableType* type = package->FindOrCreateType(name.type);
-  if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
+  if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
     diag->Error(DiagMessage(value->GetSource())
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but type '" << type->type << "' already has ID "
@@ -406,8 +405,7 @@
   }
 
   ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
-  if (res_id.is_valid() && entry->id &&
-      entry->id.value() != res_id.entry_id()) {
+  if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
     diag->Error(DiagMessage(value->GetSource())
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but resource already has ID "
@@ -441,7 +439,7 @@
     }
   }
 
-  if (res_id.is_valid()) {
+  if (res_id.is_valid_dynamic()) {
     package->id = res_id.package_id();
     type->id = res_id.type_id();
     entry->id = res_id.entry_id();
@@ -480,8 +478,7 @@
   }
 
   ResourceTablePackage* package = FindOrCreatePackage(name.package);
-  if (res_id.is_valid() && package->id &&
-      package->id.value() != res_id.package_id()) {
+  if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
     diag->Error(DiagMessage(symbol.source)
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but package '" << package->name << "' already has ID "
@@ -490,7 +487,7 @@
   }
 
   ResourceTableType* type = package->FindOrCreateType(name.type);
-  if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
+  if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
     diag->Error(DiagMessage(symbol.source)
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but type '" << type->type << "' already has ID "
@@ -499,8 +496,7 @@
   }
 
   ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
-  if (res_id.is_valid() && entry->id &&
-      entry->id.value() != res_id.entry_id()) {
+  if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
     diag->Error(DiagMessage(symbol.source)
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but resource already has ID "
@@ -509,7 +505,7 @@
     return false;
   }
 
-  if (res_id.is_valid()) {
+  if (res_id.is_valid_dynamic()) {
     package->id = res_id.package_id();
     type->id = res_id.type_id();
     entry->id = res_id.entry_id();
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 0fe966c..6b69aaf 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -158,11 +158,8 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
 };
 
-enum class PackageType { System, Vendor, App, Dynamic };
-
 class ResourceTablePackage {
  public:
-  PackageType type = PackageType::App;
   Maybe<uint8_t> id;
   std::string name;
 
@@ -241,6 +238,19 @@
   Maybe<SearchResult> FindResource(const ResourceNameRef& name);
 
   /**
+   * 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
+   * 'current' package before it is known to the ResourceTable.
+   */
+  ResourceTablePackage* FindPackage(const android::StringPiece& name);
+
+  ResourceTablePackage* FindPackageById(uint8_t id);
+
+  ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
+
+  /**
    * The string pool used by this resource table. Values that reference strings
    * must use
    * this pool to create their strings.
@@ -259,18 +269,9 @@
    */
   std::vector<std::unique_ptr<ResourceTablePackage>> packages;
 
-  /**
-   * 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
-   * 'current' package before it is known to the ResourceTable.
-   */
-  ResourceTablePackage* FindPackage(const android::StringPiece& name);
-
-  ResourceTablePackage* FindPackageById(uint8_t id);
-
-  ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
+  // Set of dynamic packages that this table may reference. Their package names get encoded
+  // into the resources.arsc along with their compile-time assigned IDs.
+  std::map<size_t, std::string> included_packages_;
 
  private:
   ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 150dc58..2d8e5a21 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -469,7 +469,7 @@
   if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
     if (value.dataType == android::Res_value::TYPE_INT_HEX) {
       ResourceId id(value.data);
-      if (id.is_valid()) {
+      if (id.is_valid_dynamic()) {
         return id;
       }
     }
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index 1cb6aa1..2763d49 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -29,7 +29,8 @@
 struct RawValueVisitor {
   virtual ~RawValueVisitor() = default;
 
-  virtual void VisitItem(Item* value) {}
+  virtual void VisitAny(Value* value) {}
+  virtual void VisitItem(Item* value) { VisitAny(value); }
   virtual void Visit(Reference* value) { VisitItem(value); }
   virtual void Visit(RawString* value) { VisitItem(value); }
   virtual void Visit(String* value) { VisitItem(value); }
@@ -38,11 +39,11 @@
   virtual void Visit(Id* value) { VisitItem(value); }
   virtual void Visit(BinaryPrimitive* value) { VisitItem(value); }
 
-  virtual void Visit(Attribute* value) {}
-  virtual void Visit(Style* value) {}
-  virtual void Visit(Array* value) {}
-  virtual void Visit(Plural* value) {}
-  virtual void Visit(Styleable* value) {}
+  virtual void Visit(Attribute* value) { VisitAny(value); }
+  virtual void Visit(Style* value) { VisitAny(value); }
+  virtual void Visit(Array* value) { VisitAny(value); }
+  virtual void Visit(Plural* value) { VisitAny(value); }
+  virtual void Visit(Styleable* value) { VisitAny(value); }
 };
 
 // NOLINT, do not add parentheses around T.
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
index 9640eb8..3dcb1eb 100644
--- a/tools/aapt2/compile/IdAssigner.h
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -26,11 +26,8 @@
 
 namespace aapt {
 
-/**
- * Assigns IDs to each resource in the table, respecting existing IDs and
- * filling in gaps
- * in between fixed ID assignments.
- */
+// Assigns IDs to each resource in the table, respecting existing IDs and
+// filling in gaps in between fixed ID assignments.
 class IdAssigner : public IResourceTableConsumer {
  public:
   IdAssigner() = default;
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
index acebeda..c877468 100644
--- a/tools/aapt2/diff/Diff.cpp
+++ b/tools/aapt2/diff/Diff.cpp
@@ -28,6 +28,8 @@
 
 class DiffContext : public IAaptContext {
  public:
+  DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {}
+
   const std::string& GetCompilationPackage() override { return empty_; }
 
   uint8_t GetPackageId() override { return 0x0; }
@@ -45,7 +47,7 @@
  private:
   std::string empty_;
   StdErrDiagnostics diagnostics_;
-  NameMangler name_mangler_ = NameMangler(NameManglerPolicy{});
+  NameMangler name_mangler_;
   SymbolTable symbol_table_;
 };
 
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 697b07f..3098458 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -217,16 +217,17 @@
 
 class PackageFlattener {
  public:
-  PackageFlattener(IAaptContext* context, ResourceTablePackage* package, bool use_sparse_entries)
+  PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
+                   const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries)
       : context_(context),
         diag_(context->GetDiagnostics()),
         package_(package),
+        shared_libs_(shared_libs),
         use_sparse_entries_(use_sparse_entries) {}
 
   bool FlattenPackage(BigBuffer* buffer) {
     ChunkWriter pkg_writer(buffer);
-    ResTable_package* pkg_header =
-        pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
+    ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
     pkg_header->id = util::HostToDevice32(package_->id.value());
 
     if (package_->name.size() >= arraysize(pkg_header->name)) {
@@ -253,6 +254,11 @@
     // Append the types.
     buffer->AppendBuffer(std::move(type_buffer));
 
+    // If there are libraries (or if the package ID is 0x00), encode a library chunk.
+    if (package_->id.value() == 0x00 || !shared_libs_->empty()) {
+      FlattenLibrarySpec(buffer);
+    }
+
     pkg_writer.Finish();
     return true;
   }
@@ -470,6 +476,10 @@
       type_pool_.MakeRef(ToString(type->type));
 
       std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
+      if (sorted_entries.empty()) {
+        continue;
+      }
+
       if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
         return false;
       }
@@ -504,9 +514,38 @@
     return true;
   }
 
+  void FlattenLibrarySpec(BigBuffer* buffer) {
+    ChunkWriter lib_writer(buffer);
+    ResTable_lib_header* lib_header =
+        lib_writer.StartChunk<ResTable_lib_header>(RES_TABLE_LIBRARY_TYPE);
+
+    const size_t num_entries = (package_->id.value() == 0x00 ? 1 : 0) + shared_libs_->size();
+    CHECK(num_entries > 0);
+
+    lib_header->count = util::HostToDevice32(num_entries);
+
+    ResTable_lib_entry* lib_entry = buffer->NextBlock<ResTable_lib_entry>(num_entries);
+    if (package_->id.value() == 0x00) {
+      // Add this package
+      lib_entry->packageId = util::HostToDevice32(0x00);
+      strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
+                    util::Utf8ToUtf16(package_->name));
+      ++lib_entry;
+    }
+
+    for (auto& map_entry : *shared_libs_) {
+      lib_entry->packageId = util::HostToDevice32(map_entry.first);
+      strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
+                    util::Utf8ToUtf16(map_entry.second));
+      ++lib_entry;
+    }
+    lib_writer.Finish();
+  }
+
   IAaptContext* context_;
   IDiagnostics* diag_;
   ResourceTablePackage* package_;
+  const std::map<size_t, std::string>* shared_libs_;
   bool use_sparse_entries_;
   StringPool type_pool_;
   StringPool key_pool_;
@@ -542,7 +581,8 @@
 
   // Flatten each package.
   for (auto& package : table->packages) {
-    PackageFlattener flattener(context, package.get(), options_.use_sparse_entries);
+    PackageFlattener flattener(context, package.get(), &table->included_packages_,
+                               options_.use_sparse_entries);
     if (!flattener.FlattenPackage(&package_buffer)) {
       return false;
     }
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index ff71742..4196187 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -56,13 +56,13 @@
       return result;
     }
 
-    if (out_table->add(content.data(), content.size(), -1, true) != NO_ERROR) {
+    if (out_table->add(content.data(), content.size(), 1, true) != NO_ERROR) {
       return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
     }
     return ::testing::AssertionSuccess();
   }
 
-  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions options,
+  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
                                      ResourceTable* table, ResourceTable* out_table) {
     std::string content;
     auto result = Flatten(context, options, table, &content);
@@ -359,4 +359,56 @@
   EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
 }
 
+TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("lib", 0x00)
+          .AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>())
+          .Build();
+  ResourceTable result;
+  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+  Maybe<ResourceTable::SearchResult> search_result =
+      result.FindResource(test::ParseNameOrDie("lib:id/foo"));
+  AAPT_ASSERT_TRUE(search_result);
+  EXPECT_EQ(0x00u, search_result.value().package->id.value());
+
+  auto iter = result.included_packages_.find(0x00);
+  ASSERT_NE(result.included_packages_.end(), iter);
+  EXPECT_EQ("lib", iter->second);
+}
+
+TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("app", 0x7f)
+          .AddValue("app:id/foo", ResourceId(0x7f010000),
+                    test::BuildReference("lib_one:id/foo", ResourceId(0x02010000)))
+          .AddValue("app:id/bar", ResourceId(0x7f010001),
+                    test::BuildReference("lib_two:id/bar", ResourceId(0x03010000)))
+          .Build();
+  table->included_packages_[0x02] = "lib_one";
+  table->included_packages_[0x03] = "lib_two";
+
+  ResTable result;
+  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+  const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
+  ASSERT_NE(nullptr, dynamic_ref_table);
+
+  const KeyedVector<String16, uint8_t> entries = dynamic_ref_table->entries();
+
+  ssize_t idx = entries.indexOfKey(android::String16("lib_one"));
+  ASSERT_GE(idx, 0);
+  EXPECT_EQ(0x02u, entries.valueAt(idx));
+
+  idx = entries.indexOfKey(android::String16("lib_two"));
+  ASSERT_GE(idx, 0);
+  EXPECT_EQ(0x03u, entries.valueAt(idx));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 53d6ea1..0cec9ae 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -22,6 +22,23 @@
 
 namespace aapt {
 
+void ClassMember::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const {
+  processor_.WriteToStream(out, prefix);
+}
+
+void MethodDefinition::AppendStatement(const StringPiece& statement) {
+  statements_.push_back(statement.to_string());
+}
+
+void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final,
+                                     std::ostream* out) const {
+  *out << prefix << signature_ << " {\n";
+  for (const auto& statement : statements_) {
+    *out << prefix << "  " << statement << "\n";
+  }
+  *out << prefix << "}";
+}
+
 bool ClassDefinition::empty() const {
   for (const std::unique_ptr<ClassMember>& member : members_) {
     if (!member->empty()) {
@@ -40,7 +57,7 @@
   ClassMember::WriteToStream(prefix, final, out);
 
   *out << prefix << "public ";
-  if (qualifier_ == ClassQualifier::Static) {
+  if (qualifier_ == ClassQualifier::kStatic) {
     *out << "static ";
   }
   *out << "final class " << name_ << " {\n";
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 64e4b29..ca76421 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -41,10 +41,11 @@
 
   virtual bool empty() const = 0;
 
+  // Writes the class member to the out stream. Subclasses should derive this method
+  // to write their own data. Call this base method from the subclass to write out
+  // this member's comments/annotations.
   virtual void WriteToStream(const android::StringPiece& prefix, bool final,
-                             std::ostream* out) const {
-    processor_.WriteToStream(out, prefix);
-  }
+                             std::ostream* out) const;
 
  private:
   AnnotationProcessor processor_;
@@ -142,7 +143,29 @@
 
 using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
 
-enum class ClassQualifier { None, Static };
+// Represents a method in a class.
+class MethodDefinition : public ClassMember {
+ public:
+  // Expected method signature example: 'public static void onResourcesLoaded(int p)'.
+  explicit MethodDefinition(const android::StringPiece& signature)
+      : signature_(signature.to_string()) {}
+
+  // Appends a single statement to the method. It should include no newlines or else
+  // formatting may be broken.
+  void AppendStatement(const android::StringPiece& statement);
+
+  // Even if the method is empty, we always want to write the method signature.
+  bool empty() const override { return false; }
+
+  void WriteToStream(const android::StringPiece& prefix, bool final,
+                     std::ostream* out) const override;
+
+ private:
+  std::string signature_;
+  std::vector<std::string> statements_;
+};
+
+enum class ClassQualifier { kNone, kStatic };
 
 class ClassDefinition : public ClassMember {
  public:
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index b71dc48..9c0f13c 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -23,6 +23,7 @@
 #include <tuple>
 
 #include "android-base/logging.h"
+#include "android-base/stringprintf.h"
 #include "androidfw/StringPiece.h"
 
 #include "NameMangler.h"
@@ -35,6 +36,7 @@
 #include "process/SymbolTable.h"
 
 using android::StringPiece;
+using android::base::StringPrintf;
 
 namespace aapt {
 
@@ -55,11 +57,9 @@
   return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
 }
 
-/*
- * Java symbols can not contain . or -, but those are valid in a resource name.
- * Replace those with '_'.
- */
-static std::string Transform(const StringPiece& symbol) {
+// Java symbols can not contain . or -, but those are valid in a resource name.
+// Replace those with '_'.
+static std::string TransformToFieldName(const StringPiece& symbol) {
   std::string output = symbol.to_string();
   for (char& c : output) {
     if (c == '.' || c == '-') {
@@ -69,34 +69,31 @@
   return output;
 }
 
-/**
- * Transforms an attribute in a styleable to the Java field name:
- *
- * <declare-styleable name="Foo">
- *   <attr name="android:bar" />
- *   <attr name="bar" />
- * </declare-styleable>
- *
- * Foo_android_bar
- * Foo_bar
- */
-static std::string TransformNestedAttr(
-    const ResourceNameRef& attr_name, const std::string& styleable_class_name,
-    const StringPiece& package_name_to_generate) {
+// Transforms an attribute in a styleable to the Java field name:
+//
+// <declare-styleable name="Foo">
+//   <attr name="android:bar" />
+//   <attr name="bar" />
+// </declare-styleable>
+//
+// Foo_android_bar
+// Foo_bar
+static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
+                                       const std::string& styleable_class_name,
+                                       const StringPiece& package_name_to_generate) {
   std::string output = styleable_class_name;
 
   // We may reference IDs from other packages, so prefix the entry name with
   // the package.
   if (!attr_name.package.empty() &&
       package_name_to_generate != attr_name.package) {
-    output += "_" + Transform(attr_name.package);
+    output += "_" + TransformToFieldName(attr_name.package);
   }
-  output += "_" + Transform(attr_name.entry);
+  output += "_" + TransformToFieldName(attr_name.entry);
   return output;
 }
 
-static void AddAttributeFormatDoc(AnnotationProcessor* processor,
-                                  Attribute* attr) {
+static void AddAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
   const uint32_t type_mask = attr->type_mask;
   if (type_mask & android::ResTable_map::TYPE_REFERENCE) {
     processor->AppendComment(
@@ -128,7 +125,7 @@
     processor->AppendComment(
         "<p>May be a color value, in the form of "
         "\"<code>#<i>rgb</i></code>\",\n"
-        "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
+        "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code>\", or \n"
         "\"<code>#<i>aarrggbb</i></code>\".");
   }
 
@@ -202,18 +199,21 @@
   return true;
 }
 
+// Whether or not to skip writing this symbol.
+bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) {
+  return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
+                     !symbol.value().is_public);
+}
+
 struct StyleableAttr {
-  const Reference* attr_ref;
+  const Reference* attr_ref = nullptr;
   std::string field_name;
-  std::unique_ptr<SymbolTable::Symbol> symbol;
+  Maybe<SymbolTable::Symbol> symbol;
 };
 
-static bool less_styleable_attr(const StyleableAttr& lhs,
-                                const StyleableAttr& rhs) {
-  const ResourceId lhs_id =
-      lhs.attr_ref->id ? lhs.attr_ref->id.value() : ResourceId(0);
-  const ResourceId rhs_id =
-      rhs.attr_ref->id ? rhs.attr_ref->id.value() : ResourceId(0);
+static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) {
+  const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0));
+  const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0));
   if (lhs_id < rhs_id) {
     return true;
   } else if (lhs_id > rhs_id) {
@@ -223,72 +223,57 @@
   }
 }
 
-void JavaClassGenerator::AddMembersToStyleableClass(
-    const StringPiece& package_name_to_generate, const std::string& entry_name,
-    const Styleable* styleable, ClassDefinition* out_styleable_class_def) {
-  const std::string class_name = Transform(entry_name);
+void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
+                                          const Styleable& styleable,
+                                          const StringPiece& package_name_to_generate,
+                                          ClassDefinition* out_class_def,
+                                          MethodDefinition* out_rewrite_method) {
+  const std::string array_field_name = TransformToFieldName(name.entry);
+  std::unique_ptr<ResourceArrayMember> array_def =
+      util::make_unique<ResourceArrayMember>(array_field_name);
 
-  std::unique_ptr<ResourceArrayMember> styleable_array_def =
-      util::make_unique<ResourceArrayMember>(class_name);
-
-  // This must be sorted by resource ID.
+  // The array must be sorted by resource ID.
   std::vector<StyleableAttr> sorted_attributes;
-  sorted_attributes.reserve(styleable->entries.size());
-  for (const auto& attr : styleable->entries) {
+  sorted_attributes.reserve(styleable.entries.size());
+  for (const auto& attr : styleable.entries) {
     // If we are not encoding final attributes, the styleable entry may have no
     // ID if we are building a static library.
     CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
     CHECK(bool(attr.name)) << "no name set for Styleable entry";
 
-    // We will need the unmangled, transformed name in the comments and the
-    // field,
+    // We will need the unmangled, transformed name in the comments and the field,
     // so create it once and cache it in this StyleableAttr data structure.
-    StyleableAttr styleable_attr = {};
+    StyleableAttr styleable_attr;
     styleable_attr.attr_ref = &attr;
-    styleable_attr.field_name = TransformNestedAttr(
-        attr.name.value(), class_name, package_name_to_generate);
 
-    Reference mangled_reference;
-    mangled_reference.id = attr.id;
-    mangled_reference.name = attr.name;
-    if (mangled_reference.name.value().package.empty()) {
-      mangled_reference.name.value().package =
-          context_->GetCompilationPackage();
-    }
+    // The field name for this attribute is prefixed by the name of this styleable and
+    // the package it comes from.
+    styleable_attr.field_name =
+        TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
 
-    if (Maybe<ResourceName> mangled_name =
-            context_->GetNameMangler()->MangleName(
-                mangled_reference.name.value())) {
-      mangled_reference.name = mangled_name;
-    }
-
-    // Look up the symbol so that we can write out in the comments what are
-    // possible
-    // legal values for this attribute.
-    const SymbolTable::Symbol* symbol =
-        context_->GetExternalSymbols()->FindByReference(mangled_reference);
+    // Look up the symbol so that we can write out in the comments what are possible legal values
+    // for this attribute.
+    const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(attr);
     if (symbol && symbol->attribute) {
-      // Copy the symbol data structure because the returned instance can be
-      // destroyed.
-      styleable_attr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol);
+      // Copy the symbol data structure because the returned instance can be destroyed.
+      styleable_attr.symbol = *symbol;
     }
     sorted_attributes.push_back(std::move(styleable_attr));
   }
 
   // Sort the attributes by ID.
-  std::sort(sorted_attributes.begin(), sorted_attributes.end(),
-            less_styleable_attr);
+  std::sort(sorted_attributes.begin(), sorted_attributes.end());
 
+  // Build the JavaDoc comment for the Styleable array. This has references to child attributes
+  // and what possible values can be used for them.
   const size_t attr_count = sorted_attributes.size();
   if (attr_count > 0) {
-    // Build the comment string for the Styleable. It includes details about the
-    // child attributes.
     std::stringstream styleable_comment;
-    if (!styleable->GetComment().empty()) {
-      styleable_comment << styleable->GetComment() << "\n";
+    if (!styleable.GetComment().empty()) {
+      styleable_comment << styleable.GetComment() << "\n";
     } else {
-      styleable_comment << "Attributes that can be used with a " << class_name
-                        << ".\n";
+      // Apply a default intro comment if the styleable has no comments of its own.
+      styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
     }
 
     styleable_comment << "<p>Includes the following attributes:</p>\n"
@@ -297,22 +282,16 @@
                          "<colgroup align=\"left\" />\n"
                          "<tr><th>Attribute</th><th>Description</th></tr>\n";
 
+    // Build the table of attributes with their links and names.
     for (const StyleableAttr& entry : sorted_attributes) {
-      if (!entry.symbol) {
+      if (SkipSymbol(entry.symbol)) {
         continue;
       }
 
-      if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
-          !entry.symbol->is_public) {
-        // Don't write entries for non-public attributes.
-        continue;
-      }
-
-      StringPiece attr_comment_line = entry.symbol->attribute->GetComment();
+      StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
       if (attr_comment_line.contains("@removed")) {
         // Removed attributes are public but hidden from the documentation, so
-        // don't emit
-        // them as part of the class documentation.
+        // don't emit them as part of the class documentation.
         continue;
       }
 
@@ -327,71 +306,52 @@
 
       styleable_comment << "<td>";
 
-      // Only use the comment up until the first '.'. This is to stay compatible
-      // with
-      // the way old AAPT did it (presumably to keep it short and to avoid
-      // including
+      // Only use the comment up until the first '.'. This is to stay compatible with
+      // the way old AAPT did it (presumably to keep it short and to avoid including
       // annotations like @hide which would affect this Styleable).
-      auto iter =
-          std::find(attr_comment_line.begin(), attr_comment_line.end(), u'.');
+      auto iter = std::find(attr_comment_line.begin(), attr_comment_line.end(), '.');
       if (iter != attr_comment_line.end()) {
-        attr_comment_line =
-            attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1);
+        attr_comment_line = attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1);
       }
       styleable_comment << attr_comment_line << "</td></tr>\n";
     }
     styleable_comment << "</table>\n";
 
+    // Generate the @see lines for each attribute.
     for (const StyleableAttr& entry : sorted_attributes) {
-      if (!entry.symbol) {
-        continue;
-      }
-
-      if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
-          !entry.symbol->is_public) {
-        // Don't write entries for non-public attributes.
+      if (SkipSymbol(entry.symbol)) {
         continue;
       }
       styleable_comment << "@see #" << entry.field_name << "\n";
     }
 
-    styleable_array_def->GetCommentBuilder()->AppendComment(
-        styleable_comment.str());
+    array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
   }
 
   // Add the ResourceIds to the array member.
   for (const StyleableAttr& styleable_attr : sorted_attributes) {
-    styleable_array_def->AddElement(styleable_attr.attr_ref->id
-                                        ? styleable_attr.attr_ref->id.value()
-                                        : ResourceId(0));
+    const ResourceId id = styleable_attr.attr_ref->id.value_or_default(ResourceId(0));
+    array_def->AddElement(id);
   }
 
   // Add the Styleable array to the Styleable class.
-  out_styleable_class_def->AddMember(std::move(styleable_array_def));
+  out_class_def->AddMember(std::move(array_def));
 
   // Now we emit the indices into the array.
   for (size_t i = 0; i < attr_count; i++) {
     const StyleableAttr& styleable_attr = sorted_attributes[i];
-
-    if (!styleable_attr.symbol) {
-      continue;
-    }
-
-    if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
-        !styleable_attr.symbol->is_public) {
-      // Don't write entries for non-public attributes.
+    if (SkipSymbol(styleable_attr.symbol)) {
       continue;
     }
 
     StringPiece comment = styleable_attr.attr_ref->GetComment();
-    if (styleable_attr.symbol->attribute && comment.empty()) {
-      comment = styleable_attr.symbol->attribute->GetComment();
+    if (styleable_attr.symbol.value().attribute && comment.empty()) {
+      comment = styleable_attr.symbol.value().attribute->GetComment();
     }
 
     if (comment.contains("@removed")) {
       // Removed attributes are public but hidden from the documentation, so
-      // don't emit them
-      // as part of the class documentation.
+      // don't emit them as part of the class documentation.
       continue;
     }
 
@@ -414,114 +374,151 @@
       std::stringstream default_comment;
       default_comment << "<p>This symbol is the offset where the "
                       << "{@link " << package_name << ".R.attr#"
-                      << Transform(attr_name.entry) << "}\n"
+                      << TransformToFieldName(attr_name.entry) << "}\n"
                       << "attribute's value can be found in the "
-                      << "{@link #" << class_name << "} array.";
+                      << "{@link #" << array_field_name << "} array.";
       attr_processor->AppendComment(default_comment.str());
     }
 
     attr_processor->AppendNewLine();
-
-    AddAttributeFormatDoc(attr_processor,
-                          styleable_attr.symbol->attribute.get());
+    AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
     attr_processor->AppendNewLine();
+    attr_processor->AppendComment(
+        StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
 
-    std::stringstream doclava_name;
-    doclava_name << "@attr name " << package_name << ":" << attr_name.entry;
+    out_class_def->AddMember(std::move(index_member));
+  }
 
-    attr_processor->AppendComment(doclava_name.str());
-
-    out_styleable_class_def->AddMember(std::move(index_member));
+  // If there is a rewrite method to generate, add the statements that rewrite package IDs
+  // for this styleable.
+  if (out_rewrite_method != nullptr) {
+    out_rewrite_method->AppendStatement(
+        StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
+    out_rewrite_method->AppendStatement(
+        StringPrintf("  if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
+    out_rewrite_method->AppendStatement(
+        StringPrintf("    styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
+                     array_field_name.data(), array_field_name.data()));
+    out_rewrite_method->AppendStatement("  }");
+    out_rewrite_method->AppendStatement("}");
   }
 }
 
-bool JavaClassGenerator::AddMembersToTypeClass(
-    const StringPiece& package_name_to_generate,
-    const ResourceTablePackage* package, const ResourceTableType* type,
-    ClassDefinition* out_type_class_def) {
-  for (const auto& entry : type->entries) {
-    if (SkipSymbol(entry->symbol_status.state)) {
+void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
+                                         const ResourceEntry& entry, ClassDefinition* out_class_def,
+                                         MethodDefinition* out_rewrite_method) {
+  const std::string field_name = TransformToFieldName(name.entry);
+  std::unique_ptr<ResourceMember> resource_member =
+      util::make_unique<ResourceMember>(field_name, id);
+
+  // Build the comments and annotations for this entry.
+  AnnotationProcessor* processor = resource_member->GetCommentBuilder();
+
+  // Add the comments from any <public> tags.
+  if (entry.symbol_status.state != SymbolState::kUndefined) {
+    processor->AppendComment(entry.symbol_status.comment);
+  }
+
+  // Add the comments from all configurations of this entry.
+  for (const auto& config_value : entry.values) {
+    processor->AppendComment(config_value->value->GetComment());
+  }
+
+  // If this is an Attribute, append the format Javadoc.
+  if (!entry.values.empty()) {
+    if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
+      // We list out the available values for the given attribute.
+      AddAttributeFormatDoc(processor, attr);
+    }
+  }
+
+  out_class_def->AddMember(std::move(resource_member));
+
+  if (out_rewrite_method != nullptr) {
+    const StringPiece& type_str = ToString(name.type);
+    out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
+                                                     type_str.data(), field_name.data(),
+                                                     type_str.data(), field_name.data()));
+  }
+}
+
+Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
+                                                        const StringPiece& package_name_to_generate,
+                                                        const ResourceEntry& entry) {
+  if (SkipSymbol(entry.symbol_status.state)) {
+    return {};
+  }
+
+  std::string unmangled_package;
+  std::string unmangled_name = entry.name;
+  if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
+    // The entry name was mangled, and we successfully unmangled it.
+    // Check that we want to emit this symbol.
+    if (package_name != unmangled_package) {
+      // Skip the entry if it doesn't belong to the package we're writing.
+      return {};
+    }
+  } else if (package_name_to_generate != package_name) {
+    // We are processing a mangled package name,
+    // but this is a non-mangled resource.
+    return {};
+  }
+  return {std::move(unmangled_name)};
+}
+
+bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
+                                     const ResourceTablePackage& package,
+                                     const ResourceTableType& type,
+                                     ClassDefinition* out_type_class_def,
+                                     MethodDefinition* out_rewrite_method_def) {
+  for (const auto& entry : type.entries) {
+    const Maybe<std::string> unmangled_name =
+        UnmangleResource(package.name, package_name_to_generate, *entry);
+    if (!unmangled_name) {
       continue;
     }
 
+    // Create an ID if there is one (static libraries don't need one).
     ResourceId id;
-    if (package->id && type->id && entry->id) {
-      id = ResourceId(package->id.value(), type->id.value(), entry->id.value());
+    if (package.id && type.id && entry->id) {
+      id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
     }
 
-    std::string unmangled_package;
-    std::string unmangled_name = entry->name;
-    if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
-      // The entry name was mangled, and we successfully unmangled it.
-      // Check that we want to emit this symbol.
-      if (package->name != unmangled_package) {
-        // Skip the entry if it doesn't belong to the package we're writing.
-        continue;
-      }
-    } else if (package_name_to_generate != package->name) {
-      // We are processing a mangled package name,
-      // but this is a non-mangled resource.
-      continue;
-    }
+    // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
+    const ResourceNameRef resource_name(
+        package_name_to_generate,
+        type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
+        unmangled_name.value());
 
-    if (!IsValidSymbol(unmangled_name)) {
-      ResourceNameRef resource_name(package_name_to_generate, type->type,
-                                    unmangled_name);
+    // Check to see if the unmangled name is a valid Java name (not a keyword).
+    if (!IsValidSymbol(unmangled_name.value())) {
       std::stringstream err;
       err << "invalid symbol name '" << resource_name << "'";
       error_ = err.str();
       return false;
     }
 
-    if (type->type == ResourceType::kStyleable) {
+    if (resource_name.type == ResourceType::kStyleable) {
       CHECK(!entry->values.empty());
 
       const Styleable* styleable =
           static_cast<const Styleable*>(entry->values.front()->value.get());
 
-      // Comments are handled within this method.
-      AddMembersToStyleableClass(package_name_to_generate, unmangled_name,
-                                 styleable, out_type_class_def);
+      ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
+                       out_rewrite_method_def);
     } else {
-      std::unique_ptr<ResourceMember> resource_member =
-          util::make_unique<ResourceMember>(Transform(unmangled_name), id);
-
-      // Build the comments and annotations for this entry.
-      AnnotationProcessor* processor = resource_member->GetCommentBuilder();
-
-      // Add the comments from any <public> tags.
-      if (entry->symbol_status.state != SymbolState::kUndefined) {
-        processor->AppendComment(entry->symbol_status.comment);
-      }
-
-      // Add the comments from all configurations of this entry.
-      for (const auto& config_value : entry->values) {
-        processor->AppendComment(config_value->value->GetComment());
-      }
-
-      // If this is an Attribute, append the format Javadoc.
-      if (!entry->values.empty()) {
-        if (Attribute* attr =
-                ValueCast<Attribute>(entry->values.front()->value.get())) {
-          // We list out the available values for the given attribute.
-          AddAttributeFormatDoc(processor, attr);
-        }
-      }
-
-      out_type_class_def->AddMember(std::move(resource_member));
+      ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def);
     }
   }
   return true;
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
-                                  std::ostream* out) {
+bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::ostream* out) {
   return Generate(package_name_to_generate, package_name_to_generate, out);
 }
 
-static void AppendJavaDocAnnotations(
-    const std::vector<std::string>& annotations,
-    AnnotationProcessor* processor) {
+static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
+                                     AnnotationProcessor* processor) {
   for (const std::string& annotation : annotations) {
     std::string proper_annotation = "@";
     proper_annotation += annotation;
@@ -532,37 +529,40 @@
 bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
                                   const StringPiece& out_package_name,
                                   std::ostream* out) {
-  ClassDefinition r_class("R", ClassQualifier::None, true);
+  ClassDefinition r_class("R", ClassQualifier::kNone, true);
+  std::unique_ptr<MethodDefinition> rewrite_method;
+
+  // Generate an onResourcesLoaded() callback if requested.
+  if (options_.generate_rewrite_callback) {
+    rewrite_method =
+        util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
+  }
 
   for (const auto& package : table_->packages) {
     for (const auto& type : package->types) {
       if (type->type == ResourceType::kAttrPrivate) {
+        // We generate these as part of the kAttr type, so skip them here.
         continue;
       }
 
+      // Stay consistent with AAPT and generate an empty type class if the R class
+      // is public.
       const bool force_creation_if_empty =
           (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
 
-      std::unique_ptr<ClassDefinition> class_def =
-          util::make_unique<ClassDefinition>(ToString(type->type),
-                                             ClassQualifier::Static,
-                                             force_creation_if_empty);
-
-      bool result = AddMembersToTypeClass(
-          package_name_to_generate, package.get(), type.get(), class_def.get());
-      if (!result) {
+      std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>(
+          ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty);
+      if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
+                       rewrite_method.get())) {
         return false;
       }
 
       if (type->type == ResourceType::kAttr) {
         // Also include private attributes in this same class.
-        ResourceTableType* priv_type =
-            package->FindType(ResourceType::kAttrPrivate);
+        const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
         if (priv_type) {
-          result =
-              AddMembersToTypeClass(package_name_to_generate, package.get(),
-                                    priv_type, class_def.get());
-          if (!result) {
+          if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
+                           rewrite_method.get())) {
             return false;
           }
         }
@@ -571,23 +571,23 @@
       if (type->type == ResourceType::kStyleable &&
           options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
         // When generating a public R class, we don't want Styleable to be part
-        // of the API.
-        // It is only emitted for documentation purposes.
+        // of the API. It is only emitted for documentation purposes.
         class_def->GetCommentBuilder()->AppendComment("@doconly");
       }
 
-      AppendJavaDocAnnotations(options_.javadoc_annotations,
-                               class_def->GetCommentBuilder());
+      AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
 
       r_class.AddMember(std::move(class_def));
     }
   }
 
-  AppendJavaDocAnnotations(options_.javadoc_annotations,
-                           r_class.GetCommentBuilder());
+  if (rewrite_method != nullptr) {
+    r_class.AddMember(std::move(rewrite_method));
+  }
 
-  if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name,
-                                      options_.use_final, out)) {
+  AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
+
+  if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out)) {
     return false;
   }
 
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 5cf556e..178f558 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -25,19 +25,22 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
 
 namespace aapt {
 
 class AnnotationProcessor;
 class ClassDefinition;
+class MethodDefinition;
 
 struct JavaClassGeneratorOptions {
-  /*
-   * Specifies whether to use the 'final' modifier
-   * on resource entries. Default is true.
-   */
+  // Specifies whether to use the 'final' modifier on resource entries. Default is true.
   bool use_final = true;
 
+  // Whether to generate code to rewrite the package ID of resources.
+  // Implies use_final == true. Default is false.
+  bool generate_rewrite_callback = false;
+
   enum class SymbolTypes {
     kAll,
     kPublicPrivate,
@@ -46,47 +49,54 @@
 
   SymbolTypes types = SymbolTypes::kAll;
 
-  /**
-   * A list of JavaDoc annotations to add to the comments of all generated
-   * classes.
-   */
+  // A list of JavaDoc annotations to add to the comments of all generated classes.
   std::vector<std::string> javadoc_annotations;
 };
 
-/*
- * Generates the R.java file for a resource table.
- */
+// Generates the R.java file for a resource table.
 class JavaClassGenerator {
  public:
   JavaClassGenerator(IAaptContext* context, ResourceTable* table,
                      const JavaClassGeneratorOptions& options);
 
-  /*
-   * Writes the R.java file to `out`. Only symbols belonging to `package` are
-   * written.
-   * All symbols technically belong to a single package, but linked libraries
-   * will
-   * have their names mangled, denoting that they came from a different package.
-   * We need to generate these symbols in a separate file.
-   * Returns true on success.
-   */
-  bool Generate(const android::StringPiece& packageNameToGenerate, std::ostream* out);
+  // Writes the R.java file to `out`. Only symbols belonging to `package` are written.
+  // All symbols technically belong to a single package, but linked libraries will
+  // have their names mangled, denoting that they came from a different package.
+  // We need to generate these symbols in a separate file. Returns true on success.
+  bool Generate(const android::StringPiece& package_name_to_generate, std::ostream* out);
 
-  bool Generate(const android::StringPiece& packageNameToGenerate,
-                const android::StringPiece& outputPackageName, std::ostream* out);
+  bool Generate(const android::StringPiece& package_name_to_generate,
+                const android::StringPiece& output_package_name, std::ostream* out);
 
   const std::string& getError() const;
 
  private:
-  bool AddMembersToTypeClass(const android::StringPiece& packageNameToGenerate,
-                             const ResourceTablePackage* package, const ResourceTableType* type,
-                             ClassDefinition* outTypeClassDef);
-
-  void AddMembersToStyleableClass(const android::StringPiece& packageNameToGenerate,
-                                  const std::string& entryName, const Styleable* styleable,
-                                  ClassDefinition* outStyleableClassDef);
-
   bool SkipSymbol(SymbolState state);
+  bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
+
+  // Returns the unmangled resource entry name if the unmangled package is the same as
+  // package_name_to_generate. Returns nothing if the resource should be skipped.
+  Maybe<std::string> UnmangleResource(const android::StringPiece& package_name,
+                                      const android::StringPiece& package_name_to_generate,
+                                      const ResourceEntry& entry);
+
+  bool ProcessType(const android::StringPiece& package_name_to_generate,
+                   const ResourceTablePackage& package, const ResourceTableType& type,
+                   ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def);
+
+  // Writes a resource to the R.java file, optionally writing out a rewrite rule for its package
+  // ID if `out_rewrite_method` is not nullptr.
+  void ProcessResource(const ResourceNameRef& name, const ResourceId& id,
+                       const ResourceEntry& entry, ClassDefinition* out_class_def,
+                       MethodDefinition* out_rewrite_method);
+
+  // Writes a styleable resource to the R.java file, optionally writing out a rewrite rule for
+  // its package ID if `out_rewrite_method` is not nullptr.
+  // `package_name_to_generate` is the package
+  void ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
+                        const Styleable& styleable,
+                        const android::StringPiece& package_name_to_generate,
+                        ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method);
 
   IAaptContext* context_;
   ResourceTable* table_;
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 55c5cb2..bcb2d4f 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -293,8 +293,7 @@
 
 TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {}
 
-TEST(JavaClassGeneratorTest,
-     CommentsForStyleablesAndNestedAttributesArePresent) {
+TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
   Attribute attr(false);
   attr.SetComment(StringPiece("This is an attribute"));
 
@@ -364,4 +363,33 @@
   EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1));
 }
 
+TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("android", 0x00)
+          .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>(false))
+          .AddValue("android:id/foo", ResourceId(0x00020000), util::make_unique<Id>())
+          .AddValue(
+              "android:style/foo", ResourceId(0x00030000),
+              test::StyleBuilder()
+                  .AddItem("android:attr/foo", ResourceId(0x00010000), util::make_unique<Id>())
+                  .Build())
+          .Build();
+
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetPackageId(0x00).SetCompilationPackage("android").Build();
+
+  JavaClassGeneratorOptions options;
+  options.use_final = false;
+  options.generate_rewrite_callback = true;
+  JavaClassGenerator generator(context.get(), table.get(), options);
+
+  std::stringstream out;
+  ASSERT_TRUE(generator.Generate("android", &out));
+
+  std::string actual = out.str();
+
+  EXPECT_NE(std::string::npos, actual.find("onResourcesLoaded"));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index de8e59a..f49e498 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -100,11 +100,9 @@
   }
 
   std::unique_ptr<ClassDefinition> permission_class =
-      util::make_unique<ClassDefinition>("permission", ClassQualifier::Static,
-                                         false);
+      util::make_unique<ClassDefinition>("permission", ClassQualifier::kStatic, false);
   std::unique_ptr<ClassDefinition> permission_group_class =
-      util::make_unique<ClassDefinition>("permission_group",
-                                         ClassQualifier::Static, false);
+      util::make_unique<ClassDefinition>("permission_group", ClassQualifier::kStatic, false);
 
   bool error = false;
   std::vector<xml::Element*> children = el->GetChildElements();
@@ -125,8 +123,7 @@
   }
 
   std::unique_ptr<ClassDefinition> manifest_class =
-      util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None,
-                                         false);
+      util::make_unique<ClassDefinition>("Manifest", ClassQualifier::kNone, false);
   manifest_class->AddMember(std::move(permission_class));
   manifest_class->AddMember(std::move(permission_group_class));
   return manifest_class;
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index f07e20b..0162461 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -59,7 +59,16 @@
 
 namespace aapt {
 
+// The type of package to build.
+enum class PackageType {
+  kApp,
+  kSharedLib,
+  kStaticLib,
+};
+
 struct LinkOptions {
+  PackageType package_type = PackageType::kApp;
+
   std::string output_path;
   std::string manifest_path;
   std::vector<std::string> include_paths;
@@ -87,7 +96,6 @@
   std::unordered_set<std::string> extensions_to_not_compress;
 
   // Static lib options.
-  bool static_lib = false;
   bool no_static_lib_packages = false;
 
   // AndroidManifest.xml massaging options.
@@ -111,7 +119,7 @@
 
 class LinkContext : public IAaptContext {
  public:
-  LinkContext() : name_mangler_({}) {}
+  LinkContext() : name_mangler_({}), symbols_(&name_mangler_) {}
 
   IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
 
@@ -684,14 +692,13 @@
 
       // First try to load the file as a static lib.
       std::string error_str;
-      std::unique_ptr<ResourceTable> static_include =
-          LoadStaticLibrary(path, &error_str);
-      if (static_include) {
-        if (!options_.static_lib) {
-          // Can't include static libraries when not building a static library.
+      std::unique_ptr<ResourceTable> include_static = LoadStaticLibrary(path, &error_str);
+      if (include_static) {
+        if (options_.package_type != PackageType::kStaticLib) {
+          // Can't include static libraries when not building a static library (they have no IDs
+          // assigned).
           context_->GetDiagnostics()->Error(
-              DiagMessage(path)
-              << "can't include static library when building app");
+              DiagMessage(path) << "can't include static library when not building a static lib");
           return false;
         }
 
@@ -699,16 +706,15 @@
         // package of this
         // table to our compilation package.
         if (options_.no_static_lib_packages) {
-          if (ResourceTablePackage* pkg =
-                  static_include->FindPackageById(0x7f)) {
+          if (ResourceTablePackage* pkg = include_static->FindPackageById(0x7f)) {
             pkg->name = context_->GetCompilationPackage();
           }
         }
 
         context_->GetExternalSymbols()->AppendSource(
-            util::make_unique<ResourceTableSymbolSource>(static_include.get()));
+            util::make_unique<ResourceTableSymbolSource>(include_static.get()));
 
-        static_table_includes_.push_back(std::move(static_include));
+        static_table_includes_.push_back(std::move(include_static));
 
       } else if (!error_str.empty()) {
         // We had an error with reading, so fail.
@@ -717,12 +723,19 @@
       }
 
       if (!asset_source->AddAssetPath(path)) {
-        context_->GetDiagnostics()->Error(DiagMessage(path)
-                                          << "failed to load include path");
+        context_->GetDiagnostics()->Error(DiagMessage(path) << "failed to load include path");
         return false;
       }
     }
 
+    // Capture the shared libraries so that the final resource table can be properly flattened
+    // with support for shared libraries.
+    for (auto& entry : asset_source->GetAssignedPackageIds()) {
+      if (entry.first > 0x01 && entry.first < 0x7f) {
+        final_table_.included_packages_[entry.first] = entry.second;
+      }
+    }
+
     context_->GetExternalSymbols()->AppendSource(std::move(asset_source));
     return true;
   }
@@ -1402,7 +1415,7 @@
    */
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set,
                 xml::XmlResource* manifest, ResourceTable* table) {
-    const bool keep_raw_values = options_.static_lib;
+    const bool keep_raw_values = options_.package_type == PackageType::kStaticLib;
     bool result = FlattenXml(manifest, "AndroidManifest.xml", {},
                              keep_raw_values, writer, context_);
     if (!result) {
@@ -1422,25 +1435,21 @@
     file_flattener_options.update_proguard_spec =
         static_cast<bool>(options_.generate_proguard_rules_path);
 
-    ResourceFileFlattener file_flattener(file_flattener_options, context_,
-                                         keep_set);
+    ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
 
     if (!file_flattener.Flatten(table, writer)) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed linking file resources");
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources");
       return false;
     }
 
-    if (options_.static_lib) {
+    if (options_.package_type == PackageType::kStaticLib) {
       if (!FlattenTableToPb(table, writer)) {
-        context_->GetDiagnostics()->Error(
-            DiagMessage() << "failed to write resources.arsc.flat");
+        context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc.flat");
         return false;
       }
     } else {
       if (!FlattenTable(table, writer)) {
-        context_->GetDiagnostics()->Error(DiagMessage()
-                                          << "failed to write resources.arsc");
+        context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc");
         return false;
       }
     }
@@ -1484,7 +1493,9 @@
 
     context_->SetNameManglerPolicy(
         NameManglerPolicy{context_->GetCompilationPackage()});
-    if (context_->GetCompilationPackage() == "android") {
+    if (options_.package_type == PackageType::kSharedLib) {
+      context_->SetPackageId(0x00);
+    } else if (context_->GetCompilationPackage() == "android") {
       context_->SetPackageId(0x01);
     } else {
       context_->SetPackageId(0x7f);
@@ -1527,7 +1538,7 @@
       return 1;
     }
 
-    if (!options_.static_lib) {
+    if (options_.package_type != PackageType::kStaticLib) {
       PrivateAttributeMover mover;
       if (!mover.Consume(context_, &final_table_)) {
         context_->GetDiagnostics()->Error(
@@ -1538,8 +1549,7 @@
       // Assign IDs if we are building a regular app.
       IdAssigner id_assigner(&options_.stable_id_map);
       if (!id_assigner.Consume(context_, &final_table_)) {
-        context_->GetDiagnostics()->Error(DiagMessage()
-                                          << "failed assigning IDs");
+        context_->GetDiagnostics()->Error(DiagMessage() << "failed assigning IDs");
         return 1;
       }
 
@@ -1586,17 +1596,15 @@
       return 1;
     }
 
-    if (options_.static_lib) {
+    if (options_.package_type == PackageType::kStaticLib) {
       if (!options_.products.empty()) {
-        context_->GetDiagnostics()
-            ->Warn(DiagMessage()
-                   << "can't select products when building static library");
+        context_->GetDiagnostics()->Warn(DiagMessage()
+                                         << "can't select products when building static library");
       }
     } else {
       ProductFilter product_filter(options_.products);
       if (!product_filter.Consume(context_, &final_table_)) {
-        context_->GetDiagnostics()->Error(DiagMessage()
-                                          << "failed stripping products");
+        context_->GetDiagnostics()->Error(DiagMessage() << "failed stripping products");
         return 1;
       }
     }
@@ -1610,7 +1618,7 @@
       }
     }
 
-    if (!options_.static_lib && context_->GetMinSdkVersion() > 0) {
+    if (options_.package_type != PackageType::kStaticLib && context_->GetMinSdkVersion() > 0) {
       if (context_->IsVerbose()) {
         context_->GetDiagnostics()->Note(
             DiagMessage() << "collapsing resource versions for minimum SDK "
@@ -1626,8 +1634,7 @@
     if (!options_.no_resource_deduping) {
       ResourceDeduper deduper;
       if (!deduper.Consume(context_, &final_table_)) {
-        context_->GetDiagnostics()->Error(DiagMessage()
-                                          << "failed deduping resources");
+        context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
         return 1;
       }
     }
@@ -1635,12 +1642,11 @@
     proguard::KeepSet proguard_keep_set;
     proguard::KeepSet proguard_main_dex_keep_set;
 
-    if (options_.static_lib) {
+    if (options_.package_type == PackageType::kStaticLib) {
       if (options_.table_splitter_options.config_filter != nullptr ||
           !options_.table_splitter_options.preferred_densities.empty()) {
-        context_->GetDiagnostics()
-            ->Warn(DiagMessage()
-                   << "can't strip resources when building static library");
+        context_->GetDiagnostics()->Warn(DiagMessage()
+                                         << "can't strip resources when building static library");
       }
     } else {
       // Adjust the SplitConstraints so that their SDK version is stripped if it
@@ -1778,10 +1784,15 @@
       options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
       options.javadoc_annotations = options_.javadoc_annotations;
 
-      if (options_.static_lib || options_.generate_non_final_ids) {
+      if (options_.package_type == PackageType::kStaticLib || options_.generate_non_final_ids) {
         options.use_final = false;
       }
 
+      if (options_.package_type == PackageType::kSharedLib) {
+        options.use_final = false;
+        options.generate_rewrite_callback = true;
+      }
+
       const StringPiece actual_package = context_->GetCompilationPackage();
       StringPiece output_package = context_->GetCompilationPackage();
       if (options_.custom_java_package) {
@@ -1850,9 +1861,11 @@
   std::vector<std::unique_ptr<io::IFileCollection>> collections_;
 
   // A vector of ResourceTables. This is here to retain ownership, so that the
-  // SymbolTable
-  // can use these.
+  // SymbolTable can use these.
   std::vector<std::unique_ptr<ResourceTable>> static_table_includes_;
+
+  // The set of shared libraries being used, mapping their assigned package ID to package name.
+  std::map<size_t, std::string> shared_libs_;
 };
 
 int Link(const std::vector<StringPiece>& args) {
@@ -1866,6 +1879,8 @@
   bool legacy_x_flag = false;
   bool require_localization = false;
   bool verbose = false;
+  bool shared_lib = false;
+  bool static_lib = false;
   Maybe<std::string> stable_id_file_path;
   std::vector<std::string> split_args;
   Flags flags =
@@ -1942,7 +1957,8 @@
                         "Version name to inject into the AndroidManifest.xml "
                         "if none is present",
                         &options.manifest_fixer_options.version_name_default)
-          .OptionalSwitch("--static-lib", "Generate a static Android library", &options.static_lib)
+          .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library", &shared_lib)
+          .OptionalSwitch("--static-lib", "Generate a static Android library", &static_lib)
           .OptionalSwitch("--no-static-lib-packages",
                           "Merge all library resources under the app's package",
                           &options.no_static_lib_packages)
@@ -2096,7 +2112,19 @@
     options.table_splitter_options.preferred_densities.push_back(preferred_density_config.density);
   }
 
-  if (!options.static_lib && stable_id_file_path) {
+  if (shared_lib && static_lib) {
+    context.GetDiagnostics()->Error(DiagMessage()
+                                    << "only one of --shared-lib and --static-lib can be defined");
+    return 1;
+  }
+
+  if (shared_lib) {
+    options.package_type = PackageType::kSharedLib;
+  } else if (static_lib) {
+    options.package_type = PackageType::kStaticLib;
+  }
+
+  if (options.package_type != PackageType::kStaticLib && stable_id_file_path) {
     if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path.value(),
                          &options.stable_id_map)) {
       return 1;
@@ -2122,7 +2150,7 @@
   }
 
   // Turn off auto versioning for static-libs.
-  if (options.static_lib) {
+  if (options.package_type == PackageType::kStaticLib) {
     options.no_auto_version = true;
     options.no_version_vectors = true;
     options.no_version_transitions = true;
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index b4cf4f8..d5c0dc4 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -54,16 +54,14 @@
   return true;
 }
 
-static bool OptionalNameIsJavaClassName(xml::Element* el,
-                                        SourcePathDiagnostics* diag) {
+static bool OptionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
   if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
     return NameIsJavaClassName(el, attr, diag);
   }
   return true;
 }
 
-static bool RequiredNameIsJavaClassName(xml::Element* el,
-                                        SourcePathDiagnostics* diag) {
+static bool RequiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
   if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
     return NameIsJavaClassName(el, attr, diag);
   }
@@ -72,6 +70,15 @@
   return false;
 }
 
+static bool RequiredNameIsJavaPackage(xml::Element* el, SourcePathDiagnostics* diag) {
+  if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
+    return util::IsJavaPackageName(attr->value);
+  }
+  diag->Error(DiagMessage(el->line_number)
+              << "<" << el->name << "> is missing attribute 'android:name'");
+  return false;
+}
+
 static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
   xml::Attribute* attr = el->FindAttribute({}, "package");
   if (!attr) {
@@ -263,7 +270,8 @@
   xml::XmlNodeAction& application_action = manifest_action["application"];
   application_action.Action(OptionalNameIsJavaClassName);
 
-  application_action["uses-library"];
+  application_action["uses-library"].Action(RequiredNameIsJavaPackage);
+  application_action["library"].Action(RequiredNameIsJavaPackage);
   application_action["meta-data"] = meta_data_action;
   application_action["activity"] = component_action;
   application_action["activity-alias"] = component_action;
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index cc07a6e..eee4b60 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -26,11 +26,9 @@
 namespace aapt {
 
 template <typename InputContainer, typename OutputIterator, typename Predicate>
-OutputIterator move_if(InputContainer& input_container, OutputIterator result,
-                       Predicate pred) {
+OutputIterator move_if(InputContainer& input_container, OutputIterator result, Predicate pred) {
   const auto last = input_container.end();
-  auto new_end =
-      std::find_if(input_container.begin(), input_container.end(), pred);
+  auto new_end = std::find_if(input_container.begin(), input_container.end(), pred);
   if (new_end == last) {
     return result;
   }
@@ -57,8 +55,7 @@
   return result;
 }
 
-bool PrivateAttributeMover::Consume(IAaptContext* context,
-                                    ResourceTable* table) {
+bool PrivateAttributeMover::Consume(IAaptContext* context, ResourceTable* table) {
   for (auto& package : table->packages) {
     ResourceTableType* type = package->FindType(ResourceType::kAttr);
     if (!type) {
@@ -68,18 +65,24 @@
     if (type->symbol_status.state != SymbolState::kPublic) {
       // No public attributes, so we can safely leave these private attributes
       // where they are.
-      return true;
+      continue;
     }
 
-    ResourceTableType* priv_attr_type =
-        package->FindOrCreateType(ResourceType::kAttrPrivate);
-    CHECK(priv_attr_type->entries.empty());
+    std::vector<std::unique_ptr<ResourceEntry>> private_attr_entries;
 
-    move_if(type->entries, std::back_inserter(priv_attr_type->entries),
+    move_if(type->entries, std::back_inserter(private_attr_entries),
             [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
               return entry->symbol_status.state != SymbolState::kPublic;
             });
-    break;
+
+    if (private_attr_entries.empty()) {
+      // No private attributes.
+      continue;
+    }
+
+    ResourceTableType* priv_attr_type = package->FindOrCreateType(ResourceType::kAttrPrivate);
+    CHECK(priv_attr_type->entries.empty());
+    priv_attr_type->entries = std::move(private_attr_entries);
   }
   return true;
 }
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 90c4922..7fcf6e7 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -54,8 +54,7 @@
   EXPECT_NE(type->FindEntry("privateB"), nullptr);
 }
 
-TEST(PrivateAttributeMoverTest,
-     LeavePrivateAttributesWhenNoPublicAttributesDefined) {
+TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
   std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
@@ -77,4 +76,23 @@
   ASSERT_EQ(type, nullptr);
 }
 
+TEST(PrivateAttributeMoverTest, DoNotCreatePrivateAttrsIfNoneExist) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddSimple("android:attr/pub")
+          .SetSymbolState("android:attr/pub", ResourceId(0x01010000), SymbolState::kPublic)
+          .Build();
+
+  ResourceTablePackage* package = table->FindPackage("android");
+  ASSERT_NE(nullptr, package);
+
+  ASSERT_EQ(nullptr, package->FindType(ResourceType::kAttrPrivate));
+
+  PrivateAttributeMover mover;
+  ASSERT_TRUE(mover.Consume(context.get(), table.get()));
+
+  ASSERT_EQ(nullptr, package->FindType(ResourceType::kAttrPrivate));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index ea68b61..4a42826 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -96,10 +96,8 @@
 
       // Find the attribute in the symbol table and check if it is visible from
       // this callsite.
-      const SymbolTable::Symbol* symbol =
-          ReferenceLinker::ResolveAttributeCheckVisibility(
-              transformed_reference, context_->GetNameMangler(), symbols_,
-              callsite_, &err_str);
+      const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility(
+          transformed_reference, symbols_, callsite_, &err_str);
       if (symbol) {
         // Assign our style key the correct ID.
         // The ID may not exist.
@@ -225,12 +223,10 @@
   return true;
 }
 
-const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(
-    const Reference& reference, NameMangler* mangler, SymbolTable* symbols) {
+const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference,
+                                                          SymbolTable* symbols) {
   if (reference.name) {
-    Maybe<ResourceName> mangled = mangler->MangleName(reference.name.value());
-    return symbols->FindByName(mangled ? mangled.value()
-                                       : reference.name.value());
+    return symbols->FindByName(reference.name.value());
   } else if (reference.id) {
     return symbols->FindById(reference.id.value());
   } else {
@@ -238,11 +234,11 @@
   }
 }
 
-const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(
-    const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
-    CallSite* callsite, std::string* out_error) {
-  const SymbolTable::Symbol* symbol =
-      ResolveSymbol(reference, name_mangler, symbols);
+const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const Reference& reference,
+                                                                         SymbolTable* symbols,
+                                                                         CallSite* callsite,
+                                                                         std::string* out_error) {
+  const SymbolTable::Symbol* symbol = ResolveSymbol(reference, symbols);
   if (!symbol) {
     if (out_error) *out_error = "not found";
     return nullptr;
@@ -256,10 +252,9 @@
 }
 
 const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility(
-    const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
-    CallSite* callsite, std::string* out_error) {
-  const SymbolTable::Symbol* symbol = ResolveSymbolCheckVisibility(
-      reference, name_mangler, symbols, callsite, out_error);
+    const Reference& reference, SymbolTable* symbols, CallSite* callsite, std::string* out_error) {
+  const SymbolTable::Symbol* symbol =
+      ResolveSymbolCheckVisibility(reference, symbols, callsite, out_error);
   if (!symbol) {
     return nullptr;
   }
@@ -271,11 +266,11 @@
   return symbol;
 }
 
-Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(
-    const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols,
-    CallSite* callsite, std::string* out_error) {
-  const SymbolTable::Symbol* symbol =
-      ResolveSymbol(reference, name_mangler, symbols);
+Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference,
+                                                               SymbolTable* symbols,
+                                                               CallSite* callsite,
+                                                               std::string* out_error) {
+  const SymbolTable::Symbol* symbol = ResolveSymbol(reference, symbols);
   if (!symbol) {
     if (out_error) *out_error = "not found";
     return {};
@@ -311,13 +306,11 @@
   CHECK(reference->name || reference->id);
 
   Reference transformed_reference = *reference;
-  TransformReferenceFromNamespace(decls, context->GetCompilationPackage(),
-                                  &transformed_reference);
+  TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), &transformed_reference);
 
   std::string err_str;
-  const SymbolTable::Symbol* s = ResolveSymbolCheckVisibility(
-      transformed_reference, context->GetNameMangler(), symbols, callsite,
-      &err_str);
+  const SymbolTable::Symbol* s =
+      ResolveSymbolCheckVisibility(transformed_reference, symbols, callsite, &err_str);
   if (s) {
     // The ID may not exist. This is fine because of the possibility of building
     // against libraries without assigned IDs.
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index bdabf24..2d18c49d 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -51,18 +51,17 @@
    * Performs name mangling and looks up the resource in the symbol table.
    * Returns nullptr if the symbol was not found.
    */
-  static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference,
-                                                  NameMangler* mangler,
-                                                  SymbolTable* symbols);
+  static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, SymbolTable* symbols);
 
   /**
    * Performs name mangling and looks up the resource in the symbol table. If
    * the symbol is not visible by the reference at the callsite, nullptr is
    * returned. out_error holds the error message.
    */
-  static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(
-      const Reference& reference, NameMangler* name_mangler,
-      SymbolTable* symbols, CallSite* callsite, std::string* out_error);
+  static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(const Reference& reference,
+                                                                 SymbolTable* symbols,
+                                                                 CallSite* callsite,
+                                                                 std::string* out_error);
 
   /**
    * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is
@@ -70,18 +69,19 @@
    * That is, the return value will have a non-null value for
    * ISymbolTable::Symbol::attribute.
    */
-  static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(
-      const Reference& reference, NameMangler* name_mangler,
-      SymbolTable* symbols, CallSite* callsite, std::string* out_error);
+  static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(const Reference& reference,
+                                                                    SymbolTable* symbols,
+                                                                    CallSite* callsite,
+                                                                    std::string* out_error);
 
   /**
    * Resolves the attribute reference and returns an xml::AaptAttribute if
    * successful.
    * If resolution fails, outError holds the error message.
    */
-  static Maybe<xml::AaptAttribute> CompileXmlAttribute(
-      const Reference& reference, NameMangler* name_mangler,
-      SymbolTable* symbols, CallSite* callsite, std::string* out_error);
+  static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference,
+                                                       SymbolTable* symbols, CallSite* callsite,
+                                                       std::string* out_error);
 
   /**
    * Writes the resource name to the DiagMessage, using the
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 1dbe53c..b839862 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -107,8 +107,8 @@
         attr_ref.private_reference = maybe_package.value().private_namespace;
 
         std::string err_str;
-        attr.compiled_attribute = ReferenceLinker::CompileXmlAttribute(
-            attr_ref, context_->GetNameMangler(), symbols_, callsite_, &err_str);
+        attr.compiled_attribute =
+            ReferenceLinker::CompileXmlAttribute(attr_ref, symbols_, callsite_, &err_str);
 
         if (!attr.compiled_attribute) {
           context_->GetDiagnostics()->Error(DiagMessage(source) << "attribute '"
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 1a3da73..5d75e76 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -16,10 +16,15 @@
 
 #include "process/SymbolTable.h"
 
+#include <iostream>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
 #include "androidfw/AssetManager.h"
 #include "androidfw/ResourceTypes.h"
 
 #include "ConfigDescription.h"
+#include "NameMangler.h"
 #include "Resource.h"
 #include "ResourceUtils.h"
 #include "ValueVisitor.h"
@@ -45,25 +50,49 @@
 }
 
 const SymbolTable::Symbol* SymbolTable::FindByName(const ResourceName& name) {
-  if (const std::shared_ptr<Symbol>& s = cache_.get(name)) {
+  const ResourceName* name_with_package = &name;
+
+  // Fill in the package name if necessary.
+  // If there is no package in `name`, we will need to copy the ResourceName
+  // and store it somewhere; we use the Maybe<> class to reserve storage.
+  Maybe<ResourceName> name_with_package_impl;
+  if (name.package.empty()) {
+    name_with_package_impl = ResourceName(mangler_->GetTargetPackageName(), name.type, name.entry);
+    name_with_package = &name_with_package_impl.value();
+  }
+
+  // We store the name unmangled in the cache, so look it up as-is.
+  if (const std::shared_ptr<Symbol>& s = cache_.get(*name_with_package)) {
     return s.get();
   }
 
-  // We did not find it in the cache, so look through the sources.
+  // The name was not found in the cache. Mangle it (if necessary) and find it in our sources.
+  // Again, here we use a Maybe<> object to reserve storage if we need to mangle.
+  const ResourceName* mangled_name = name_with_package;
+  Maybe<ResourceName> mangled_name_impl;
+  if (mangler_->ShouldMangle(name_with_package->package)) {
+    mangled_name_impl = mangler_->MangleName(*name_with_package);
+    mangled_name = &mangled_name_impl.value();
+  }
+
   for (auto& symbolSource : sources_) {
-    std::unique_ptr<Symbol> symbol = symbolSource->FindByName(name);
+    std::unique_ptr<Symbol> symbol = symbolSource->FindByName(*mangled_name);
     if (symbol) {
       // Take ownership of the symbol into a shared_ptr. We do this because
-      // LruCache
-      // doesn't support unique_ptr.
-      std::shared_ptr<Symbol> shared_symbol =
-          std::shared_ptr<Symbol>(symbol.release());
-      cache_.put(name, shared_symbol);
+      // LruCache doesn't support unique_ptr.
+      std::shared_ptr<Symbol> shared_symbol(std::move(symbol));
+
+      // Since we look in the cache with the unmangled, but package prefixed
+      // name, we must put the same name into the cache.
+      cache_.put(*name_with_package, shared_symbol);
 
       if (shared_symbol->id) {
         // The symbol has an ID, so we can also cache this!
         id_cache_.put(shared_symbol->id.value(), shared_symbol);
       }
+
+      // Returns the raw pointer. Callers are not expected to hold on to this
+      // between calls to Find*.
       return shared_symbol.get();
     }
   }
@@ -79,12 +108,13 @@
   for (auto& symbolSource : sources_) {
     std::unique_ptr<Symbol> symbol = symbolSource->FindById(id);
     if (symbol) {
-      // Take ownership of the symbol into a shared_ptr. We do this because
-      // LruCache
+      // Take ownership of the symbol into a shared_ptr. We do this because LruCache
       // doesn't support unique_ptr.
-      std::shared_ptr<Symbol> shared_symbol =
-          std::shared_ptr<Symbol>(symbol.release());
+      std::shared_ptr<Symbol> shared_symbol(std::move(symbol));
       id_cache_.put(id, shared_symbol);
+
+      // Returns the raw pointer. Callers are not expected to hold on to this
+      // between calls to Find*.
       return shared_symbol.get();
     }
   }
@@ -92,16 +122,12 @@
 }
 
 const SymbolTable::Symbol* SymbolTable::FindByReference(const Reference& ref) {
-  // First try the ID. This is because when we lookup by ID, we only fill in the
-  // ID cache.
-  // Looking up by name fills in the name and ID cache. So a cache miss will
-  // cause a failed
-  // ID lookup, then a successful name lookup. Subsequent look ups will hit
-  // immediately
+  // First try the ID. This is because when we lookup by ID, we only fill in the ID cache.
+  // Looking up by name fills in the name and ID cache. So a cache miss will cause a failed
+  // ID lookup, then a successful name lookup. Subsequent look ups will hit immediately
   // because the ID is cached too.
   //
-  // If we looked up by name first, a cache miss would mean we failed to lookup
-  // by name, then
+  // If we looked up by name first, a cache miss would mean we failed to lookup by name, then
   // succeeded to lookup by ID. Subsequent lookups will miss then hit.
   const SymbolTable::Symbol* symbol = nullptr;
   if (ref.id) {
@@ -120,25 +146,21 @@
   if (!result) {
     if (name.type == ResourceType::kAttr) {
       // Recurse and try looking up a private attribute.
-      return FindByName(
-          ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
+      return FindByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
     }
     return {};
   }
 
   ResourceTable::SearchResult sr = result.value();
 
-  std::unique_ptr<SymbolTable::Symbol> symbol =
-      util::make_unique<SymbolTable::Symbol>();
+  std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
   symbol->is_public = (sr.entry->symbol_status.state == SymbolState::kPublic);
 
   if (sr.package->id && sr.type->id && sr.entry->id) {
-    symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(),
-                            sr.entry->id.value());
+    symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
   }
 
-  if (name.type == ResourceType::kAttr ||
-      name.type == ResourceType::kAttrPrivate) {
+  if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
     const ConfigDescription kDefaultConfig;
     ResourceConfigValue* config_value = sr.entry->FindValue(kDefaultConfig);
     if (config_value) {
@@ -155,8 +177,18 @@
 
 bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
   int32_t cookie = 0;
-  return assets_.addAssetPath(android::String8(path.data(), path.size()),
-                              &cookie);
+  return assets_.addAssetPath(android::String8(path.data(), path.size()), &cookie);
+}
+
+std::map<size_t, std::string> AssetManagerSymbolSource::GetAssignedPackageIds() const {
+  std::map<size_t, std::string> package_map;
+  const android::ResTable& table = assets_.getResources(false);
+  const size_t package_count = table.getBasePackageCount();
+  for (size_t i = 0; i < package_count; i++) {
+    package_map[table.getBasePackageId(i)] =
+        util::Utf16ToUtf8(android::StringPiece16(table.getBasePackageName(i).string()));
+  }
+  return package_map;
 }
 
 static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable(
@@ -170,8 +202,7 @@
   }
 
   // We found a resource.
-  std::unique_ptr<SymbolTable::Symbol> s =
-      util::make_unique<SymbolTable::Symbol>();
+  std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>();
   s->id = id;
 
   // Check to see if it is an attribute.
@@ -204,8 +235,7 @@
         return nullptr;
       }
 
-      Maybe<ResourceName> parsed_name =
-          ResourceUtils::ToResourceName(entry_name);
+      Maybe<ResourceName> parsed_name = ResourceUtils::ToResourceName(entry_name);
       if (!parsed_name) {
         return nullptr;
       }
@@ -246,8 +276,7 @@
   }
 
   if (s) {
-    s->is_public =
-        (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+    s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
     return s;
   }
   return {};
@@ -282,8 +311,7 @@
   }
 
   if (s) {
-    s->is_public =
-        (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+    s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
     return s;
   }
   return {};
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index cf597bb..298da4d 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -47,6 +47,7 @@
 }
 
 class ISymbolSource;
+class NameMangler;
 
 class SymbolTable {
  public:
@@ -72,25 +73,32 @@
     bool is_public = false;
   };
 
-  SymbolTable() : cache_(200), id_cache_(200) {}
+  SymbolTable(NameMangler* mangler) : mangler_(mangler), cache_(200), id_cache_(200) {}
 
+  // Appends a symbol source. The cache is not cleared since entries that
+  // have already been found would take precedence due to ordering.
   void AppendSource(std::unique_ptr<ISymbolSource> source);
+
+  // Prepends a symbol source so that its symbols take precedence. This will
+  // cause the existing cache to be cleared.
   void PrependSource(std::unique_ptr<ISymbolSource> source);
 
-  /**
-   * Never hold on to the result between calls to FindByName or FindById. The
-   * results stored in a cache which may evict entries.
-   */
+  // NOTE: Never hold on to the result between calls to FindByXXX. The
+  // results are stored in a cache which may evict entries on subsequent calls.
   const Symbol* FindByName(const ResourceName& name);
+
+  // NOTE: Never hold on to the result between calls to FindByXXX. The
+  // results are stored in a cache which may evict entries on subsequent calls.
   const Symbol* FindById(const ResourceId& id);
 
-  /**
-   * Let's the ISymbolSource decide whether looking up by name or ID is faster,
-   * if both are available.
-   */
+  // Let's the ISymbolSource decide whether looking up by name or ID is faster,
+  // if both are available.
+  // NOTE: Never hold on to the result between calls to FindByXXX. The
+  // results are stored in a cache which may evict entries on subsequent calls.
   const Symbol* FindByReference(const Reference& ref);
 
  private:
+  NameMangler* mangler_;
   std::vector<std::unique_ptr<ISymbolSource>> sources_;
 
   // We use shared_ptr because unique_ptr is not supported and
@@ -155,6 +163,7 @@
   AssetManagerSymbolSource() = default;
 
   bool AddAssetPath(const android::StringPiece& path);
+  std::map<size_t, std::string> GetAssignedPackageIds() const;
 
   std::unique_ptr<SymbolTable::Symbol> FindByName(
       const ResourceName& name) override;
diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp
index 9ea0786..bba316f 100644
--- a/tools/aapt2/process/SymbolTable_test.cpp
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -55,4 +55,19 @@
   EXPECT_NE(nullptr, s->attribute);
 }
 
+TEST(SymbolTableTest, FindByName) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddSimple("com.android.app:id/foo")
+          .AddSimple("com.android.app:id/" + NameMangler::MangleEntry("com.android.lib", "foo"))
+          .Build();
+
+  NameMangler mangler(NameManglerPolicy{"com.android.app", {"com.android.lib"}});
+  SymbolTable symbol_table(&mangler);
+  symbol_table.AppendSource(util::make_unique<ResourceTableSymbolSource>(table.get()));
+
+  EXPECT_NE(nullptr, symbol_table.FindByName(test::ParseNameOrDie("id/foo")));
+  EXPECT_NE(nullptr, symbol_table.FindByName(test::ParseNameOrDie("com.android.lib:id/foo")));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 8bc4e8c..8fa12d0 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,7 +1,12 @@
 # Android Asset Packaging Tool 2.0 (AAPT2) release notes
 
+## Version 2.8
+### `aapt2 link ...`
+- Adds shared library support. Build a shared library with the `--shared-lib` flag.
+  Build a client of a shared library by simply including it via `-I`.
+
 ## Version 2.7
-### `aapt2 compile`
+### `aapt2 compile ...`
 - Fixes bug where psuedolocalization auto-translated strings marked 'translateable="false"'.
 
 ## Version 2.6
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 63e5f16..557cd1b 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -33,7 +33,7 @@
 
 class Context : public IAaptContext {
  public:
-  Context() = default;
+  Context() : name_mangler_({}), symbols_(&name_mangler_), min_sdk_version_(0) {}
 
   SymbolTable* GetExternalSymbols() override { return &symbols_; }
 
@@ -63,9 +63,9 @@
   Maybe<std::string> compilation_package_;
   Maybe<uint8_t> package_id_;
   StdErrDiagnostics diagnostics_;
+  NameMangler name_mangler_;
   SymbolTable symbols_;
-  NameMangler name_mangler_ = NameMangler({});
-  int min_sdk_version_ = 0;
+  int min_sdk_version_;
 };
 
 class ContextBuilder {
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 7098fe9..29a921c 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -248,6 +248,12 @@
         }
         break;
 
+      case android::RES_TABLE_LIBRARY_TYPE:
+        if (!ParseLibrary(parser.chunk())) {
+          return false;
+        }
+        break;
+
       default:
         context_->GetDiagnostics()->Warn(
             DiagMessage(source_)
@@ -395,6 +401,21 @@
   return true;
 }
 
+bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) {
+  DynamicRefTable dynamic_ref_table;
+  if (dynamic_ref_table.load(reinterpret_cast<const ResTable_lib_header*>(chunk)) != NO_ERROR) {
+    return false;
+  }
+
+  const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table.entries();
+  const size_t count = entries.size();
+  for (size_t i = 0; i < count; i++) {
+    table_->included_packages_[entries.valueAt(i)] =
+        util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string()));
+  }
+  return true;
+}
+
 std::unique_ptr<Item> BinaryResourceParser::ParseValue(
     const ResourceNameRef& name, const ConfigDescription& config,
     const Res_value* value, uint16_t flags) {
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index dc668fd..e3dd802 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -61,6 +61,7 @@
   bool ParseTypeSpec(const android::ResChunk_header* chunk);
   bool ParseType(const ResourceTablePackage* package,
                  const android::ResChunk_header* chunk);
+  bool ParseLibrary(const android::ResChunk_header* chunk);
 
   std::unique_ptr<Item> ParseValue(const ResourceNameRef& name,
                                    const ConfigDescription& config,