Optimize FilterApkAssets by caching config

ResTable_config of every ResTable_type is read from device every time
AssetManager::RebuildFilterList is invoked. For large APKs (like
framework-res.apk), this causes a large number of page faults
when accessing the config from disk. The configs are also used in the
slow path of AssetManager::FindEntryInternal, which makes it even
slower. Instead cache the config on the TypeSpec of its ApkAsset.

Bug: 177247024
Test: libandroidfw_tests
Change-Id: I66d507c4eeb2399f7558f3d9dfc53c157129ada0
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3f06000..03ab62f 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -103,10 +103,10 @@
 }
 
 bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
-                                 bool invalidate_caches, bool filter_incompatible_configs) {
+                                 bool invalidate_caches) {
   apk_assets_ = apk_assets;
   BuildDynamicRefTable();
-  RebuildFilterList(filter_incompatible_configs);
+  RebuildFilterList();
   if (invalidate_caches) {
     InvalidateCaches(static_cast<uint32_t>(-1));
   }
@@ -623,7 +623,8 @@
       if (UNLIKELY(logging_enabled)) {
         last_resolution_.steps.push_back(
             Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(),
-                             overlay_result->package_name});
+                             overlay_result->package_name,
+                             overlay_result->cookie});
       }
     }
   }
@@ -646,22 +647,21 @@
   const LoadedPackage* best_package = nullptr;
   incfs::verified_map_ptr<ResTable_type> best_type;
   const ResTable_config* best_config = nullptr;
-  ResTable_config best_config_copy;
   uint32_t best_offset = 0U;
   uint32_t type_flags = 0U;
 
-  auto resolution_type = Resolution::Step::Type::NO_ENTRY;
   std::vector<Resolution::Step> resolution_steps;
 
-  // If desired_config is the same as the set configuration, then we can use our filtered list
-  // and we don't need to match the configurations, since they already matched.
-  const bool use_fast_path = !ignore_configuration && &desired_config == &configuration_;
+  // If `desired_config` is not the same as the set configuration or the caller will accept a value
+  // from any configuration, then we cannot use our filtered list of types since it only it contains
+  // types matched to the set configuration.
+  const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
 
   const size_t package_count = package_group.packages_.size();
   for (size_t pi = 0; pi < package_count; pi++) {
     const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
     const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
-    ApkAssetsCookie cookie = package_group.cookies_[pi];
+    const ApkAssetsCookie cookie = package_group.cookies_[pi];
 
     // If the type IDs are offset in this package, we need to take that into account when searching
     // for a type.
@@ -670,130 +670,82 @@
       continue;
     }
 
+    // Allow custom loader packages to overlay resource values with configurations equivalent to the
+    // current best configuration.
+    const bool package_is_loader = loaded_package->IsCustomLoader();
+
     auto entry_flags = type_spec->GetFlagsForEntryIndex(entry_idx);
     if (UNLIKELY(!entry_flags.has_value())) {
       return base::unexpected(entry_flags.error());
     }
     type_flags |= entry_flags.value();
 
-    // If the package is an overlay or custom loader,
-    // then even configurations that are the same MUST be chosen.
-    const bool package_is_loader = loaded_package->IsCustomLoader();
+    const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
+    const size_t type_entry_count = (use_filtered) ? filtered_group.type_entries.size()
+                                                   : type_spec->type_entries.size();
+    for (size_t i = 0; i < type_entry_count; i++) {
+      const TypeSpec::TypeEntry* type_entry = (use_filtered) ? filtered_group.type_entries[i]
+                                                             : &type_spec->type_entries[i];
 
-    if (use_fast_path) {
-      const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
-      for (const auto& type_config : filtered_group.type_configs) {
-        const ResTable_config& this_config = type_config.config;
-
-        // We can skip calling ResTable_config::match() because we know that all candidate
-        // configurations that do NOT match have been filtered-out.
-        if (best_config == nullptr) {
-          resolution_type = Resolution::Step::Type::INITIAL;
-        } else if (this_config.isBetterThan(*best_config, &desired_config)) {
-          resolution_type = (package_is_loader) ? Resolution::Step::Type::BETTER_MATCH_LOADER
-                                                : Resolution::Step::Type::BETTER_MATCH;
-        } else if (package_is_loader && this_config.compare(*best_config) == 0) {
-          resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
-        } else {
-          if (UNLIKELY(logging_enabled)) {
-            resolution_type = (package_is_loader) ? Resolution::Step::Type::SKIPPED_LOADER
-                                                  : Resolution::Step::Type::SKIPPED;
-            resolution_steps.push_back(Resolution::Step{resolution_type,
-                                                        this_config.toString(),
-                                                        &loaded_package->GetPackageName()});
-          }
-          continue;
-        }
-
-        // The configuration matches and is better than the previous selection.
-        // Find the entry value if it exists for this configuration.
-        const auto& type = type_config.type;
-        const auto offset = LoadedPackage::GetEntryOffset(type, entry_idx);
-        if (UNLIKELY(IsIOError(offset))) {
-          return base::unexpected(offset.error());
-        }
-        if (!offset.has_value()) {
-          if (UNLIKELY(logging_enabled)) {
-            if (package_is_loader) {
-              resolution_type = Resolution::Step::Type::NO_ENTRY_LOADER;
-            } else {
-              resolution_type = Resolution::Step::Type::NO_ENTRY;
-            }
-            resolution_steps.push_back(Resolution::Step{resolution_type,
-                                                        this_config.toString(),
-                                                        &loaded_package->GetPackageName()});
-          }
-          continue;
-        }
-
-        best_cookie = cookie;
-        best_package = loaded_package;
-        best_type = type;
-        best_config = &this_config;
-        best_offset = offset.value();
-
-        if (UNLIKELY(logging_enabled)) {
-          last_resolution_.steps.push_back(Resolution::Step{resolution_type,
-                                                            this_config.toString(),
-                                                            &loaded_package->GetPackageName()});
-        }
+      // We can skip calling ResTable_config::match() if the caller does not care for the
+      // configuration to match or if we're using the list of types that have already had their
+      // configuration matched.
+      const ResTable_config& this_config = type_entry->config;
+      if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+        continue;
       }
-    } else {
-      // This is the slower path, which doesn't use the filtered list of configurations.
-      // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness
-      // and fill in any new fields that did not exist when the APK was compiled.
-      // Furthermore when selecting configurations we can't just record the pointer to the
-      // ResTable_config, we must copy it.
-      const auto iter_end = type_spec->types + type_spec->type_count;
-      for (auto iter = type_spec->types; iter != iter_end; ++iter) {
-        const incfs::verified_map_ptr<ResTable_type>& type = *iter;
 
-        ResTable_config this_config{};
-        if (!ignore_configuration) {
-          this_config.copyFromDtoH(type->config);
-          if (!this_config.match(desired_config)) {
-            continue;
-          }
-
-          if (best_config == nullptr) {
-            resolution_type = Resolution::Step::Type::INITIAL;
-          } else if (this_config.isBetterThan(*best_config, &desired_config)) {
-            resolution_type = (package_is_loader) ? Resolution::Step::Type::BETTER_MATCH_LOADER
-                                                  : Resolution::Step::Type::BETTER_MATCH;
-          } else if (package_is_loader && this_config.compare(*best_config) == 0) {
-            resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
-          } else {
-            continue;
-          }
-        }
-
-        // The configuration matches and is better than the previous selection.
-        // Find the entry value if it exists for this configuration.
-        const auto offset = LoadedPackage::GetEntryOffset(type, entry_idx);
-        if (UNLIKELY(IsIOError(offset))) {
-          return base::unexpected(offset.error());
-        }
-        if (!offset.has_value()) {
-          continue;
-        }
-
-        best_cookie = cookie;
-        best_package = loaded_package;
-        best_type = type;
-        best_config_copy = this_config;
-        best_config = &best_config_copy;
-        best_offset = offset.value();
-
-        if (stop_at_first_match) {
-          // Any configuration will suffice, so break.
-          break;
-        }
-
+      Resolution::Step::Type resolution_type;
+      if (best_config == nullptr) {
+        resolution_type = Resolution::Step::Type::INITIAL;
+      } else if (this_config.isBetterThan(*best_config, &desired_config)) {
+        resolution_type = Resolution::Step::Type::BETTER_MATCH;
+      } else if (package_is_loader && this_config.compare(*best_config) == 0) {
+        resolution_type = Resolution::Step::Type::OVERLAID;
+      } else {
         if (UNLIKELY(logging_enabled)) {
-          last_resolution_.steps.push_back(Resolution::Step{resolution_type,
-                                                            this_config.toString(),
-                                                            &loaded_package->GetPackageName()});
+          resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
+                                                      this_config.toString(),
+                                                      &loaded_package->GetPackageName(),
+                                                      cookie});
         }
+        continue;
+      }
+
+      // The configuration matches and is better than the previous selection.
+      // Find the entry value if it exists for this configuration.
+      const auto& type = type_entry->type;
+      const auto offset = LoadedPackage::GetEntryOffset(type, entry_idx);
+      if (UNLIKELY(IsIOError(offset))) {
+        return base::unexpected(offset.error());
+      }
+
+      if (!offset.has_value()) {
+        if (UNLIKELY(logging_enabled)) {
+          resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
+                                                      this_config.toString(),
+                                                      &loaded_package->GetPackageName(),
+                                                      cookie});
+        }
+        continue;
+      }
+
+      best_cookie = cookie;
+      best_package = loaded_package;
+      best_type = type;
+      best_config = &this_config;
+      best_offset = offset.value();
+
+      if (UNLIKELY(logging_enabled)) {
+        last_resolution_.steps.push_back(Resolution::Step{resolution_type,
+                                                          this_config.toString(),
+                                                          &loaded_package->GetPackageName(),
+                                                          cookie});
+      }
+
+      // Any configuration will suffice, so break.
+      if (stop_at_first_match) {
+        break;
       }
     }
   }
@@ -851,19 +803,16 @@
     return {};
   }
 
-  auto cookie = last_resolution_.cookie;
+  const ApkAssetsCookie cookie = last_resolution_.cookie;
   if (cookie == kInvalidCookie) {
     LOG(ERROR) << "AssetManager hasn't resolved a resource to read resolution path.";
     return {};
   }
 
-  uint32_t resid = last_resolution_.resid;
-  std::vector<Resolution::Step>& steps = last_resolution_.steps;
+  const uint32_t resid = last_resolution_.resid;
+  const auto package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
+
   std::string resource_name_string;
-
-  const LoadedPackage* package =
-          apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
-
   if (package != nullptr) {
     auto resource_name = ToResourceName(last_resolution_.type_string_ref,
                                         last_resolution_.entry_string_ref,
@@ -878,44 +827,24 @@
             << "\n\tFor config -"
             << configuration_.toString();
 
-  std::string prefix;
-  for (Resolution::Step step : steps) {
-    switch (step.type) {
-      case Resolution::Step::Type::INITIAL:
-        prefix = "Found initial";
-        break;
-      case Resolution::Step::Type::BETTER_MATCH:
-        prefix = "Found better";
-        break;
-      case Resolution::Step::Type::BETTER_MATCH_LOADER:
-        prefix = "Found better in loader";
-        break;
-      case Resolution::Step::Type::OVERLAID:
-        prefix = "Overlaid";
-        break;
-      case Resolution::Step::Type::OVERLAID_LOADER:
-        prefix = "Overlaid by loader";
-        break;
-      case Resolution::Step::Type::SKIPPED:
-        prefix = "Skipped";
-        break;
-      case Resolution::Step::Type::SKIPPED_LOADER:
-        prefix = "Skipped loader";
-        break;
-      case Resolution::Step::Type::NO_ENTRY:
-        prefix = "No entry";
-        break;
-      case Resolution::Step::Type::NO_ENTRY_LOADER:
-        prefix = "No entry for loader";
-        break;
+  for (const Resolution::Step& step : last_resolution_.steps) {
+    const static std::unordered_map<Resolution::Step::Type, const char*> kStepStrings = {
+        {Resolution::Step::Type::INITIAL, "Found initial"},
+        {Resolution::Step::Type::BETTER_MATCH, "Found better"},
+        {Resolution::Step::Type::OVERLAID, "Overlaid"},
+        {Resolution::Step::Type::SKIPPED, "Skipped"},
+        {Resolution::Step::Type::NO_ENTRY, "No entry"}
+    };
+
+    const auto prefix = kStepStrings.find(step.type);
+    if (prefix == kStepStrings.end()) {
+      continue;
     }
 
-    if (!prefix.empty()) {
-      log_stream << "\n\t" << prefix << ": " << *step.package_name;
-
-      if (!step.config_name.isEmpty()) {
-        log_stream << " -" << step.config_name;
-      }
+    log_stream << "\n\t" << prefix->second << ": " << *step.package_name << " ("
+               << apk_assets_[step.cookie]->GetPath() << ")";
+    if (!step.config_name.isEmpty()) {
+      log_stream << " -" << step.config_name;
     }
   }
 
@@ -935,6 +864,16 @@
                         *result->package_name);
 }
 
+base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceTypeSpecFlags(
+    uint32_t resid) const {
+  auto result = FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */,
+                          true /* ignore_configuration */);
+  if (!result.has_value()) {
+    return base::unexpected(result.error());
+  }
+  return result->type_flags;
+}
+
 base::expected<AssetManager2::SelectedValue, NullOrIOError> AssetManager2::GetResource(
     uint32_t resid, bool may_be_bag, uint16_t density_override) const {
   auto result = FindEntry(resid, density_override, false /* stop_at_first_match */,
@@ -1333,7 +1272,7 @@
   return base::unexpected(std::nullopt);
 }
 
-void AssetManager2::RebuildFilterList(bool filter_incompatible_configs) {
+void AssetManager2::RebuildFilterList() {
   for (PackageGroup& group : package_groups_) {
     for (ConfiguredPackage& impl : group.packages_) {
       // Destroy it.
@@ -1343,14 +1282,11 @@
       new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
 
       // Create the filters here.
-      impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) {
-        FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index);
-        const auto iter_end = spec->types + spec->type_count;
-        for (auto iter = spec->types; iter != iter_end; ++iter) {
-          ResTable_config this_config;
-          this_config.copyFromDtoH((*iter)->config);
-          if (!filter_incompatible_configs || this_config.match(configuration_)) {
-            group.type_configs.push_back(TypeConfig{*iter, this_config});
+      impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
+        FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1);
+        for (const auto& type_entry : type_spec.type_entries) {
+          if (type_entry.config.match(configuration_)) {
+            group.type_entries.push_back(&type_entry);
           }
         }
       });
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 2fc3b05..996b424 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -33,7 +33,6 @@
 #endif
 #endif
 
-#include "androidfw/ByteBucketArray.h"
 #include "androidfw/Chunk.h"
 #include "androidfw/ResourceUtils.h"
 #include "androidfw/Util.h"
@@ -49,36 +48,24 @@
 // Builder that helps accumulate Type structs and then create a single
 // contiguous block of memory to store both the TypeSpec struct and
 // the Type structs.
-class TypeSpecPtrBuilder {
- public:
-  explicit TypeSpecPtrBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header)
-      : header_(header) {
-  }
+struct TypeSpecBuilder {
+  explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {}
 
   void AddType(incfs::verified_map_ptr<ResTable_type> type) {
-    types_.push_back(type);
+    TypeSpec::TypeEntry& entry = type_entries.emplace_back();
+    entry.config.copyFromDtoH(type->config);
+    entry.type = type;
   }
 
-  TypeSpecPtr Build() {
-    // Check for overflow.
-    using ElementType = incfs::verified_map_ptr<ResTable_type>;
-    if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) <
-        types_.size()) {
-      return {};
-    }
-    TypeSpec* type_spec =
-        (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
-    type_spec->type_spec = header_;
-    type_spec->type_count = types_.size();
-    memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
-    return TypeSpecPtr(type_spec);
+  TypeSpec Build() {
+    return {header_, std::move(type_entries)};
   }
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);
+  DISALLOW_COPY_AND_ASSIGN(TypeSpecBuilder);
 
   incfs::verified_map_ptr<ResTable_typeSpec> header_;
-  std::vector<incfs::verified_map_ptr<ResTable_type>> types_;
+  std::vector<TypeSpec::TypeEntry> type_entries;
 };
 
 }  // namespace
@@ -322,15 +309,10 @@
 }
 
 base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
-    bool exclude_mipmap, std::set<ResTable_config>* out_configs) const {
-  const size_t type_count = type_specs_.size();
-  for (size_t i = 0; i < type_count; i++) {
-    const TypeSpecPtr& type_spec = type_specs_[i];
-    if (type_spec == nullptr) {
-      continue;
-    }
+    bool exclude_mipmap, std::set<ResTable_config>* out_configs) const {\
+  for (const auto& type_spec : type_specs_) {
     if (exclude_mipmap) {
-      const int type_idx = type_spec->type_spec->id - 1;
+      const int type_idx = type_spec.first - 1;
       const auto type_name16 = type_string_pool_.stringAt(type_idx);
       if (UNLIKELY(IsIOError(type_name16))) {
         return base::unexpected(GetIOError(type_name16.error()));
@@ -354,11 +336,8 @@
       }
     }
 
-    const auto iter_end = type_spec->types + type_spec->type_count;
-    for (auto iter = type_spec->types; iter != iter_end; ++iter) {
-      ResTable_config config;
-      config.copyFromDtoH((*iter)->config);
-      out_configs->insert(config);
+    for (const auto& type_entry : type_spec.second.type_entries) {
+      out_configs->insert(type_entry.config);
     }
   }
   return {};
@@ -366,19 +345,12 @@
 
 void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const {
   char temp_locale[RESTABLE_MAX_LOCALE_LEN];
-  const size_t type_count = type_specs_.size();
-  for (size_t i = 0; i < type_count; i++) {
-    const TypeSpecPtr& type_spec = type_specs_[i];
-    if (type_spec != nullptr) {
-      const auto iter_end = type_spec->types + type_spec->type_count;
-      for (auto iter = type_spec->types; iter != iter_end; ++iter) {
-        ResTable_config configuration;
-        configuration.copyFromDtoH((*iter)->config);
-        if (configuration.locale != 0) {
-          configuration.getBcp47Locale(temp_locale, canonicalize);
-          std::string locale(temp_locale);
-          out_locales->insert(std::move(locale));
-        }
+  for (const auto& type_spec : type_specs_) {
+    for (const auto& type_entry : type_spec.second.type_entries) {
+      if (type_entry.config.locale != 0) {
+        type_entry.config.getBcp47Locale(temp_locale, canonicalize);
+        std::string locale(temp_locale);
+        out_locales->insert(std::move(locale));
       }
     }
   }
@@ -398,14 +370,13 @@
     return base::unexpected(key_idx.error());
   }
 
-  const TypeSpec* type_spec = type_specs_[*type_idx].get();
+  const TypeSpec* type_spec = GetTypeSpecByTypeIndex(*type_idx);
   if (type_spec == nullptr) {
     return base::unexpected(std::nullopt);
   }
 
-  const auto iter_end = type_spec->types + type_spec->type_count;
-  for (auto iter = type_spec->types; iter != iter_end; ++iter) {
-    const incfs::verified_map_ptr<ResTable_type>& type = *iter;
+  for (const auto& type_entry : type_spec->type_entries) {
+    const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type;
 
     size_t entry_count = dtohl(type->entryCount);
     for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
@@ -492,7 +463,7 @@
   // A map of TypeSpec builders, each associated with an type index.
   // We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
   // contiguous block of memory that holds all the Types together with the TypeSpec.
-  std::unordered_map<int, std::unique_ptr<TypeSpecPtrBuilder>> type_builder_map;
+  std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map;
 
   ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
   while (iter.HasNext()) {
@@ -562,9 +533,9 @@
           return {};
         }
 
-        std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1];
+        std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id];
         if (builder_ptr == nullptr) {
-          builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec.verified());
+          builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified());
           loaded_package->resource_ids_.set(type_spec->id, entry_count);
         } else {
           LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
@@ -584,7 +555,7 @@
         }
 
         // Type chunks must be preceded by their TypeSpec chunks.
-        std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1];
+        std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id];
         if (builder_ptr != nullptr) {
           builder_ptr->AddType(type.verified());
         } else {
@@ -722,14 +693,9 @@
 
   // Flatten and construct the TypeSpecs.
   for (auto& entry : type_builder_map) {
-    uint8_t type_idx = static_cast<uint8_t>(entry.first);
-    TypeSpecPtr type_spec_ptr = entry.second->Build();
-    if (type_spec_ptr == nullptr) {
-      LOG(ERROR) << "Too many type configurations, overflow detected.";
-      return {};
-    }
-
-    loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr);
+    TypeSpec type_spec = entry.second->Build();
+    uint8_t type_id = static_cast<uint8_t>(entry.first);
+    loaded_package->type_specs_[type_id] = std::move(type_spec);
   }
 
   return std::move(loaded_package);
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index a92694c..6fbd6aa 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -101,12 +101,7 @@
   // Only pass invalidate_caches=false when it is known that the structure
   // change in ApkAssets is due to a safe addition of resources with completely
   // new resource IDs.
-  //
-  // Only pass in filter_incompatible_configs=false when you want to load all
-  // configurations (including incompatible ones) such as when constructing an
-  // idmap.
-  bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true,
-          bool filter_incompatible_configs = true);
+  bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
 
   inline const std::vector<const ApkAssets*> GetApkAssets() const {
     return apk_assets_;
@@ -298,6 +293,12 @@
   // data failed.
   base::expected<const ResolvedBag*, NullOrIOError> ResolveBag(SelectedValue& value) const;
 
+  // Returns the android::ResTable_typeSpec flags of the resource ID.
+  //
+  // Returns a null error if the resource could not be resolved, or an I/O error if reading
+  // resource data failed.
+  base::expected<uint32_t, NullOrIOError> GetResourceTypeSpecFlags(uint32_t resid) const;
+
   const std::vector<uint32_t> GetBagResIdStack(uint32_t resid) const;
 
   // Resets the resource resolution structures in preparation for the next resource retrieval.
@@ -330,15 +331,10 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(AssetManager2);
 
-  struct TypeConfig {
-    incfs::verified_map_ptr<ResTable_type> type;
-    ResTable_config config;
-  };
-
   // A collection of configurations and their associated ResTable_type that match the current
   // AssetManager configuration.
   struct FilteredConfigGroup {
-      std::vector<TypeConfig> type_configs;
+      std::vector<const TypeSpec::TypeEntry*> type_entries;
   };
 
   // Represents an single package.
@@ -413,7 +409,7 @@
 
   // Triggers the re-construction of lists of types that match the set configuration.
   // This should always be called when mutating the AssetManager's configuration or ApkAssets set.
-  void RebuildFilterList(bool filter_incompatible_configs = true);
+  void RebuildFilterList();
 
   // Retrieves the APK paths of overlays that overlay non-system packages.
   std::set<std::string> GetNonSystemOverlayPaths() const;
@@ -460,13 +456,9 @@
       enum class Type {
         INITIAL,
         BETTER_MATCH,
-        BETTER_MATCH_LOADER,
         OVERLAID,
-        OVERLAID_LOADER,
         SKIPPED,
-        SKIPPED_LOADER,
         NO_ENTRY,
-        NO_ENTRY_LOADER,
       };
 
       // Marks what kind of override this step was.
@@ -477,6 +469,9 @@
 
       // Marks the package name of the better resource found in this step.
       const std::string* package_name;
+
+      //
+      ApkAssetsCookie cookie = kInvalidCookie;
     };
 
     // Last resolved resource ID.
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 17d97a2..891fb90 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -47,18 +47,19 @@
 // TypeSpec is going to be immediately proceeded by
 // an array of Type structs, all in the same block of memory.
 struct TypeSpec {
-  // Pointer to the mmapped data where flags are kept.
-  // Flags denote whether the resource entry is public
-  // and under which configurations it varies.
+  struct TypeEntry {
+    incfs::verified_map_ptr<ResTable_type> type;
+
+    // Type configurations are accessed frequently when setting up an AssetManager and querying
+    // resources. Access this cached configuration to minimize page faults.
+    ResTable_config config;
+  };
+
+  // Pointer to the mmapped data where flags are kept. Flags denote whether the resource entry is
+  // public and under which configurations it varies.
   incfs::verified_map_ptr<ResTable_typeSpec> type_spec;
 
-  // The number of types that follow this struct.
-  // There is a type for each configuration that entries are defined for.
-  size_t type_count;
-
-  // Trick to easily access a variable number of Type structs
-  // proceeding this struct, and to ensure their alignment.
-  incfs::verified_map_ptr<ResTable_type> types[0];
+  std::vector<TypeEntry> type_entries;
 
   base::expected<uint32_t, NullOrIOError> GetFlagsForEntryIndex(uint16_t entry_index) const {
     if (entry_index >= dtohl(type_spec->entryCount)) {
@@ -92,11 +93,6 @@
   PROPERTY_OVERLAY = 1U << 3U,
 };
 
-// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of
-// ResTable_type pointers.
-// TypeSpecPtr is a managed pointer that knows how to delete itself.
-using TypeSpecPtr = util::unique_cptr<TypeSpec>;
-
 struct OverlayableInfo {
   std::string name;
   std::string actor;
@@ -239,17 +235,17 @@
   inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const {
     // If the type IDs are offset in this package, we need to take that into account when searching
     // for a type.
-    return type_specs_[type_index - type_id_offset_].get();
+    const auto& type_spec = type_specs_.find(type_index + 1 - type_id_offset_);
+    if (type_spec == type_specs_.end()) {
+      return nullptr;
+    }
+    return &type_spec->second;
   }
 
   template <typename Func>
   void ForEachTypeSpec(Func f) const {
-    for (size_t i = 0; i < type_specs_.size(); i++) {
-      const TypeSpecPtr& ptr = type_specs_[i];
-      if (ptr != nullptr) {
-        uint8_t type_id = ptr->type_spec->id;
-        f(ptr.get(), type_id - 1);
-      }
+    for (const auto& type_spec : type_specs_) {
+      f(type_spec.second, type_spec.first);
     }
   }
 
@@ -289,7 +285,7 @@
   int type_id_offset_ = 0;
   package_property_t property_flags_ = 0U;
 
-  ByteBucketArray<TypeSpecPtr> type_specs_;
+  std::unordered_map<uint8_t, TypeSpec> type_specs_;
   ByteBucketArray<uint32_t> resource_ids_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
   std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 471b0ee..e1c0fab7 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -55,6 +55,12 @@
     basic_de_fr_assets_ = ApkAssets::Load("basic/basic_de_fr.apk");
     ASSERT_NE(nullptr, basic_de_fr_assets_);
 
+    basic_xhdpi_assets_ = ApkAssets::Load("basic/basic_xhdpi-v4.apk");
+    ASSERT_NE(nullptr, basic_de_fr_assets_);
+
+    basic_xxhdpi_assets_ = ApkAssets::Load("basic/basic_xxhdpi-v4.apk");
+    ASSERT_NE(nullptr, basic_de_fr_assets_);
+
     style_assets_ = ApkAssets::Load("styles/styles.apk");
     ASSERT_NE(nullptr, style_assets_);
 
@@ -87,6 +93,8 @@
  protected:
   std::unique_ptr<const ApkAssets> basic_assets_;
   std::unique_ptr<const ApkAssets> basic_de_fr_assets_;
+  std::unique_ptr<const ApkAssets> basic_xhdpi_assets_;
+  std::unique_ptr<const ApkAssets> basic_xxhdpi_assets_;
   std::unique_ptr<const ApkAssets> style_assets_;
   std::unique_ptr<const ApkAssets> lib_one_assets_;
   std::unique_ptr<const ApkAssets> lib_two_assets_;
@@ -225,6 +233,24 @@
   ASSERT_EQ("com.android.lib_one:string/foo", ToFormattedResourceString(*name));
 }
 
+TEST_F(AssetManager2Test, GetResourceNameNonMatchingConfig) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({basic_de_fr_assets_.get()});
+
+  auto value = assetmanager.GetResourceName(basic::R::string::test1);
+  ASSERT_TRUE(value.has_value());
+  EXPECT_EQ("com.android.basic:string/test1", ToFormattedResourceString(*value));
+}
+
+TEST_F(AssetManager2Test, GetResourceTypeSpecFlags) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({basic_de_fr_assets_.get()});
+
+  auto value = assetmanager.GetResourceTypeSpecFlags(basic::R::string::test1);
+  ASSERT_TRUE(value.has_value());
+  EXPECT_EQ(ResTable_typeSpec::SPEC_PUBLIC | ResTable_config::CONFIG_LOCALE, *value);
+}
+
 TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_.get()});
@@ -442,6 +468,29 @@
   EXPECT_EQ(*low_ref, value->resid);
 }
 
+TEST_F(AssetManager2Test, DensityOverride) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({basic_assets_.get(), basic_xhdpi_assets_.get(),
+                             basic_xxhdpi_assets_.get()});
+  assetmanager.SetConfiguration({
+    .density = ResTable_config::DENSITY_XHIGH,
+    .sdkVersion = 21,
+  });
+
+  auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
+  ASSERT_TRUE(value.has_value());
+  EXPECT_EQ(Res_value::TYPE_STRING, value->type);
+  EXPECT_EQ("xhdpi", GetStringFromPool(assetmanager.GetStringPoolForCookie(value->cookie),
+                                       value->data));
+
+  value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/,
+                                   ResTable_config::DENSITY_XXHIGH);
+  ASSERT_TRUE(value.has_value());
+  EXPECT_EQ(Res_value::TYPE_STRING, value->type);
+  EXPECT_EQ("xxhdpi", GetStringFromPool(assetmanager.GetStringPoolForCookie(value->cookie),
+                                        value->data));
+}
+
 TEST_F(AssetManager2Test, KeepLastReferenceIdUnmodifiedIfNoReferenceIsResolved) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_.get()});
@@ -716,7 +765,7 @@
 
   auto result = assetmanager.GetLastResourceResolution();
   EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
-            "\tFor config -de\n\tFound initial: com.android.basic", result);
+            "\tFor config -de\n\tFound initial: com.android.basic (basic/basic.apk)", result);
 }
 
 TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) {
@@ -736,8 +785,8 @@
   auto result = assetmanager.GetLastResourceResolution();
   EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
             "\tFor config -de\n"
-            "\tFound initial: com.android.basic\n"
-            "\tFound better: com.android.basic -de", result);
+            "\tFound initial: com.android.basic (basic/basic.apk)\n"
+            "\tFound better: com.android.basic (basic/basic_de_fr.apk) -de", result);
 }
 
 TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 6357411..9aa3634 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -65,10 +65,10 @@
 
   const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
   ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
 
-  auto type = type_spec->types[0];
-  ASSERT_TRUE(LoadedPackage::GetEntry(type, entry_index).has_value());
+  auto type = type_spec->type_entries[0];
+  ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
 }
 
 TEST(LoadedArscTest, LoadSparseEntryApp) {
@@ -89,10 +89,10 @@
 
   const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
   ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
 
-  auto type = type_spec->types[0];
-  ASSERT_TRUE(LoadedPackage::GetEntry(type, entry_index).has_value());
+  auto type = type_spec->type_entries[0];
+  ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
 }
 
 TEST(LoadedArscTest, LoadSharedLibrary) {
@@ -176,13 +176,13 @@
 
   const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
   ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
 
   auto type_name16 = package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1);
   ASSERT_TRUE(type_name16.has_value());
   EXPECT_THAT(util::Utf16ToUtf8(*type_name16), StrEq("string"));
 
-  ASSERT_TRUE(LoadedPackage::GetEntry(type_spec->types[0], entry_index).has_value());
+  ASSERT_TRUE(LoadedPackage::GetEntry(type_spec->type_entries[0].type, entry_index).has_value());
 }
 
 // AAPT(2) generates resource tables with chunks in a certain order. The rule is that
@@ -217,11 +217,11 @@
 
   const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0);
   ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
 
   type_spec = package->GetTypeSpecByTypeIndex(1);
   ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
 }
 
 TEST(LoadedArscTest, LoadOverlayable) {
@@ -363,10 +363,10 @@
 
   const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
   ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
 
-  auto type = type_spec->types[0];
-  ASSERT_TRUE(LoadedPackage::GetEntry(type, entry_index).has_value());
+  auto type = type_spec->type_entries[0];
+  ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
 }
 
 // structs with size fields (like Res_value, ResTable_entry) should be