Add function to return path for last resolved resource
After an AssetManager.FindEntry call is made, either directly or from any of the resource entry calls, a stack of the steps taken to resolve the resource is saved. Those steps can be retrieved as a log later on by calling AssetManager.GetLastResourceResolution, which returns a formatted string of the resource ID/name and path taken, including the configs and package names of each step.
Logging and the saving of the steps to memory can be enabled/disabled with the @hide .setResourceResolutionLoggingEnabled() method on AssetManager.
Bug: 122374289
Test: cases for single and multi ApkAssets loaded
Test: case for no resolution made
Test: made test app to display log on device
Test: added debugging call to source and ran through on-device apps
Change-Id: I6a32b8d4020c3f8510032ff7f431510089fff43f
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index f1a4db2..9e0a9ba 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -732,6 +732,38 @@
}
}
+ /**
+ * Enable resource resolution logging to track the steps taken to resolve the last resource
+ * entry retrieved. Stores the configuration and package names for each step.
+ *
+ * Default disabled.
+ *
+ * @param enabled Boolean indicating whether to enable or disable logging.
+ *
+ * @hide
+ */
+ public void setResourceResolutionLoggingEnabled(boolean enabled) {
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetResourceResolutionLoggingEnabled(mObject, enabled);
+ }
+ }
+
+ /**
+ * Retrieve the last resource resolution path logged.
+ *
+ * @return Formatted string containing last resource ID/name and steps taken to resolve final
+ * entry, including configuration and package names.
+ *
+ * @hide
+ */
+ public @Nullable String getLastResourceResolution() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetLastResourceResolution(mObject);
+ }
+ }
+
CharSequence getPooledStringForCookie(int cookie, int id) {
// Cookies map to ApkAssets starting at 1.
return getApkAssets()[cookie - 1].getStringFromPool(id);
@@ -1383,6 +1415,8 @@
private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
+ private static native void nativeSetResourceResolutionLoggingEnabled(long ptr, boolean enabled);
+ private static native @Nullable String nativeGetLastResourceResolution(long ptr);
// Style attribute retrieval native methods.
private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 365ceac..c4b315e 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2010,22 +2010,36 @@
public String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
return mResourcesImpl.getResourceTypeName(resid);
}
-
+
/**
* Return the entry name for a given resource identifier.
- *
+ *
* @param resid The resource identifier whose entry name is to be
* retrieved.
- *
+ *
* @return A string holding the entry name of the resource.
- *
+ *
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
- *
+ *
* @see #getResourceName
*/
public String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
return mResourcesImpl.getResourceEntryName(resid);
}
+
+ /**
+ * Return formatted log of the last retrieved resource's resolution path.
+ *
+ * @return A string holding a formatted log of the steps taken to resolve the last resource.
+ *
+ * @throws NotFoundException Throws NotFoundException if there hasn't been a resource
+ * resolved yet.
+ *
+ * @hide
+ */
+ public String getLastResourceResolution() throws NotFoundException {
+ return mResourcesImpl.getLastResourceResolution();
+ }
/**
* Parse a series of {@link android.R.styleable#Extra <extra>} tags from
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 2ad4f62..77796d9 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -299,6 +299,13 @@
}
@NonNull
+ String getLastResourceResolution() throws NotFoundException {
+ String str = mAssets.getLastResourceResolution();
+ if (str != null) return str;
+ throw new NotFoundException("Associated AssetManager hasn't resolved a resource");
+ }
+
+ @NonNull
CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {
PluralRules rule = getPluralRule();
CharSequence res = mAssets.getResourceBagText(id,
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 7b564ae..4101c04 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -44,6 +44,8 @@
#include "androidfw/MutexGuard.h"
#include "androidfw/PosixUtils.h"
#include "androidfw/ResourceTypes.h"
+#include "androidfw/ResourceUtils.h"
+
#include "core_jni_helpers.h"
#include "jni.h"
#include "nativehelper/JNIHelp.h"
@@ -975,34 +977,7 @@
return nullptr;
}
- std::string result;
- if (name.package != nullptr) {
- result.append(name.package, name.package_len);
- }
-
- if (name.type != nullptr || name.type16 != nullptr) {
- if (!result.empty()) {
- result += ":";
- }
-
- if (name.type != nullptr) {
- result.append(name.type, name.type_len);
- } else {
- result += util::Utf16ToUtf8(StringPiece16(name.type16, name.type_len));
- }
- }
-
- if (name.entry != nullptr || name.entry16 != nullptr) {
- if (!result.empty()) {
- result += "/";
- }
-
- if (name.entry != nullptr) {
- result.append(name.entry, name.entry_len);
- } else {
- result += util::Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len));
- }
- }
+ std::string result = ToFormattedResourceString(&name);
return env->NewStringUTF(result.c_str());
}
@@ -1049,6 +1024,26 @@
return nullptr;
}
+static void NativeSetResourceResolutionLoggingEnabled(JNIEnv* /*env*/,
+ jclass /*clazz*/,
+ jlong ptr,
+ jboolean enabled) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ assetmanager->SetResourceResolutionLoggingEnabled(enabled);
+}
+
+static jstring NativeGetLastResourceResolution(JNIEnv* env,
+ jclass /*clazz*/,
+ jlong ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::string resolution = assetmanager->GetLastResourceResolution();
+ if (resolution.empty()) {
+ return nullptr;
+ } else {
+ return env->NewStringUTF(resolution.c_str());
+ }
+}
+
static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr,
jboolean exclude_system) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
@@ -1452,6 +1447,10 @@
{"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
{"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
{"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+ {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
+ (void*) NativeSetResourceResolutionLoggingEnabled},
+ {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
+ (void*) NativeGetLastResourceResolution},
{"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
{"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
(void*)NativeGetSizeConfigurations},
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ad9ec02..3c35d9b 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -20,8 +20,9 @@
#include <algorithm>
#include <iterator>
-#include <set>
#include <map>
+#include <set>
+#include <sstream>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
@@ -372,6 +373,9 @@
uint32_t best_offset = 0u;
uint32_t type_flags = 0u;
+ Resolution::Step::Type resolution_type;
+ 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 = desired_config == &configuration_;
@@ -403,8 +407,8 @@
// If the package is an overlay, then even configurations that are the same MUST be chosen.
const bool package_is_overlay = loaded_package->IsOverlay();
- const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
if (use_fast_path) {
+ const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
const size_t type_count = candidate_configs.size();
for (uint32_t i = 0; i < type_count; i++) {
@@ -412,21 +416,34 @@
// 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 || this_config.isBetterThan(*best_config, desired_config)) ||
- (package_is_overlay && this_config.compare(*best_config) == 0)) {
- // The configuration matches and is better than the previous selection.
- // Find the entry value if it exists for this configuration.
- const ResTable_type* type_chunk = filtered_group.types[i];
- const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
- if (offset == ResTable_type::NO_ENTRY) {
- continue;
- }
+ 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_overlay && this_config.compare(*best_config) == 0) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ } else {
+ continue;
+ }
- best_cookie = cookie;
- best_package = loaded_package;
- best_type = type_chunk;
- best_config = &this_config;
- best_offset = offset;
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const ResTable_type* type = filtered_group.types[i];
+ const uint32_t offset = LoadedPackage::GetEntryOffset(type, local_entry_idx);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ best_cookie = cookie;
+ best_package = loaded_package;
+ best_type = type;
+ best_config = &this_config;
+ best_offset = offset;
+
+ if (resource_resolution_logging_enabled_) {
+ resolution_steps.push_back(Resolution::Step{resolution_type,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
}
}
} else {
@@ -440,23 +457,38 @@
ResTable_config this_config;
this_config.copyFromDtoH((*iter)->config);
- if (this_config.match(*desired_config)) {
- if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
- (package_is_overlay && this_config.compare(*best_config) == 0)) {
- // The configuration matches and is better than the previous selection.
- // Find the entry value if it exists for this configuration.
- const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
- if (offset == ResTable_type::NO_ENTRY) {
- continue;
- }
+ if (!this_config.match(*desired_config)) {
+ continue;
+ }
- best_cookie = cookie;
- best_package = loaded_package;
- best_type = *iter;
- best_config_copy = this_config;
- best_config = &best_config_copy;
- best_offset = offset;
- }
+ 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_overlay && this_config.compare(*best_config) == 0) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ } else {
+ continue;
+ }
+
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ best_cookie = cookie;
+ best_package = loaded_package;
+ best_type = *iter;
+ best_config_copy = this_config;
+ best_config = &best_config_copy;
+ best_offset = offset;
+
+ if (resource_resolution_logging_enabled_) {
+ resolution_steps.push_back(Resolution::Step{resolution_type,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
}
}
}
@@ -478,9 +510,95 @@
out_entry->entry_string_ref =
StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
+
+ if (resource_resolution_logging_enabled_) {
+ last_resolution.resid = resid;
+ last_resolution.cookie = best_cookie;
+ last_resolution.steps = resolution_steps;
+
+ // Cache only the type/entry refs since that's all that's needed to build name
+ last_resolution.type_string_ref =
+ StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
+ last_resolution.entry_string_ref =
+ StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
+ }
+
return best_cookie;
}
+void AssetManager2::SetResourceResolutionLoggingEnabled(bool enabled) {
+ resource_resolution_logging_enabled_ = enabled;
+
+ if (!enabled) {
+ last_resolution.cookie = kInvalidCookie;
+ last_resolution.resid = 0;
+ last_resolution.steps.clear();
+ last_resolution.type_string_ref = StringPoolRef();
+ last_resolution.entry_string_ref = StringPoolRef();
+ }
+}
+
+std::string AssetManager2::GetLastResourceResolution() const {
+ if (!resource_resolution_logging_enabled_) {
+ LOG(ERROR) << "Must enable resource resolution logging before getting path.";
+ return std::string();
+ }
+
+ auto cookie = last_resolution.cookie;
+ if (cookie == kInvalidCookie) {
+ LOG(ERROR) << "AssetManager hasn't resolved a resource to read resolution path.";
+ return std::string();
+ }
+
+ uint32_t resid = last_resolution.resid;
+ std::vector<Resolution::Step>& steps = last_resolution.steps;
+
+ ResourceName resource_name;
+ std::string resource_name_string;
+
+ const LoadedPackage* package =
+ apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
+
+ if (package != nullptr) {
+ ToResourceName(last_resolution.type_string_ref,
+ last_resolution.entry_string_ref,
+ package,
+ &resource_name);
+ resource_name_string = ToFormattedResourceString(&resource_name);
+ }
+
+ std::stringstream log_stream;
+ log_stream << base::StringPrintf("Resolution for 0x%08x ", resid)
+ << resource_name_string
+ << "\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::OVERLAID:
+ prefix = "Overlaid";
+ break;
+ }
+
+ if (!prefix.empty()) {
+ log_stream << "\n\t" << prefix << ": " << *step.package_name;
+
+ if (!step.config_name.isEmpty()) {
+ log_stream << " -" << step.config_name;
+ }
+ }
+ }
+
+ return log_stream.str();
+}
+
bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const {
FindEntryResult entry;
ApkAssetsCookie cookie =
@@ -495,27 +613,10 @@
return false;
}
- out_name->package = package->GetPackageName().data();
- out_name->package_len = package->GetPackageName().size();
-
- out_name->type = entry.type_string_ref.string8(&out_name->type_len);
- out_name->type16 = nullptr;
- if (out_name->type == nullptr) {
- out_name->type16 = entry.type_string_ref.string16(&out_name->type_len);
- if (out_name->type16 == nullptr) {
- return false;
- }
- }
-
- out_name->entry = entry.entry_string_ref.string8(&out_name->entry_len);
- out_name->entry16 = nullptr;
- if (out_name->entry == nullptr) {
- out_name->entry16 = entry.entry_string_ref.string16(&out_name->entry_len);
- if (out_name->entry16 == nullptr) {
- return false;
- }
- }
- return true;
+ return ToResourceName(entry.type_string_ref,
+ entry.entry_string_ref,
+ package,
+ out_name);
}
bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const {
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index d63feb01..645984d 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -48,4 +48,65 @@
!(has_type_separator && out_type->empty());
}
+bool ToResourceName(StringPoolRef& type_string_ref,
+ StringPoolRef& entry_string_ref,
+ const LoadedPackage* package,
+ AssetManager2::ResourceName* out_name) {
+ out_name->package = package->GetPackageName().data();
+ out_name->package_len = package->GetPackageName().size();
+
+ out_name->type = type_string_ref.string8(&out_name->type_len);
+ out_name->type16 = nullptr;
+ if (out_name->type == nullptr) {
+ out_name->type16 = type_string_ref.string16(&out_name->type_len);
+ if (out_name->type16 == nullptr) {
+ return false;
+ }
+ }
+
+ out_name->entry = entry_string_ref.string8(&out_name->entry_len);
+ out_name->entry16 = nullptr;
+ if (out_name->entry == nullptr) {
+ out_name->entry16 = entry_string_ref.string16(&out_name->entry_len);
+ if (out_name->entry16 == nullptr) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::string ToFormattedResourceString(AssetManager2::ResourceName* resource_name) {
+ std::string result;
+ if (resource_name->package != nullptr) {
+ result.append(resource_name->package, resource_name->package_len);
+ }
+
+ if (resource_name->type != nullptr || resource_name->type16 != nullptr) {
+ if (!result.empty()) {
+ result += ":";
+ }
+
+ if (resource_name->type != nullptr) {
+ result.append(resource_name->type, resource_name->type_len);
+ } else {
+ result += util::Utf16ToUtf8(StringPiece16(resource_name->type16, resource_name->type_len));
+ }
+ }
+
+ if (resource_name->entry != nullptr || resource_name->entry16 != nullptr) {
+ if (!result.empty()) {
+ result += "/";
+ }
+
+ if (resource_name->entry != nullptr) {
+ result.append(resource_name->entry, resource_name->entry_len);
+ } else {
+ result += util::Utf16ToUtf8(StringPiece16(resource_name->entry16, resource_name->entry_len));
+ }
+ }
+
+ return result;
+}
+
} // namespace android
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 0d49298..f29769b 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -229,6 +229,14 @@
ResTable_config* in_out_selected_config, uint32_t* in_out_flags,
uint32_t* out_last_reference) const;
+ // Enables or disables resource resolution logging. Clears stored steps when
+ // disabled.
+ void SetResourceResolutionLoggingEnabled(bool enabled);
+
+ // Returns formatted log of last resource resolution path, or empty if no
+ // resource has been resolved yet.
+ std::string GetLastResourceResolution() const;
+
// Retrieves the best matching bag/map resource with ID `resid`.
// This method will resolve all parent references for this bag and merge keys with the child.
// To iterate over the keys, use the following idiom:
@@ -346,6 +354,48 @@
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
+
+ // Whether or not to save resource resolution steps
+ bool resource_resolution_logging_enabled_ = false;
+
+ struct Resolution {
+
+ struct Step {
+
+ enum class Type {
+ INITIAL,
+ BETTER_MATCH,
+ OVERLAID
+ };
+
+ // Marks what kind of override this step was.
+ Type type;
+
+ // Built name of configuration for this step.
+ String8 config_name;
+
+ // Marks the package name of the better resource found in this step.
+ const std::string* package_name;
+ };
+
+ // Last resolved resource ID.
+ uint32_t resid;
+
+ // Last resolved resource result cookie.
+ ApkAssetsCookie cookie = kInvalidCookie;
+
+ // Last resolved resource type.
+ StringPoolRef type_string_ref;
+
+ // Last resolved resource entry.
+ StringPoolRef entry_string_ref;
+
+ // Steps taken to resolve last resource.
+ std::vector<Step> steps;
+ };
+
+ // Record of the last resolved resource's resolution path.
+ mutable Resolution last_resolution;
};
class Theme {
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index d94779b..eb6eb8e 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -17,6 +17,7 @@
#ifndef ANDROIDFW_RESOURCEUTILS_H
#define ANDROIDFW_RESOURCEUTILS_H
+#include "androidfw/AssetManager2.h"
#include "androidfw/StringPiece.h"
namespace android {
@@ -27,6 +28,17 @@
bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
StringPiece* out_entry);
+// Convert a type_string_ref, entry_string_ref, and package
+// to AssetManager2::ResourceName. Useful for getting
+// resource name without re-running AssetManager2::FindEntry searches.
+bool ToResourceName(StringPoolRef& type_string_ref,
+ StringPoolRef& entry_string_ref,
+ const LoadedPackage* package,
+ AssetManager2::ResourceName* out_name);
+
+// Formats a ResourceName to "package:type/entry_name".
+std::string ToFormattedResourceString(AssetManager2::ResourceName* resource_name);
+
inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) {
return (resid & 0x00ffffffu) | (static_cast<uint32_t>(package_id) << 24);
}
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 5449a54..105dcd2 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -586,4 +586,111 @@
EXPECT_THAT(asset_dir->getFileType(2), Eq(FileType::kFileTypeDirectory));
}
+TEST_F(AssetManager2Test, GetLastPathWithoutEnablingReturnsEmpty) {
+ ResTable_config desired_config;
+
+ AssetManager2 assetmanager;
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get()});
+ assetmanager.SetResourceResolutionLoggingEnabled(false);
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ auto result = assetmanager.GetLastResourceResolution();
+ EXPECT_EQ("", result);
+}
+
+TEST_F(AssetManager2Test, GetLastPathWithoutResolutionReturnsEmpty) {
+ ResTable_config desired_config;
+
+ AssetManager2 assetmanager;
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ auto result = assetmanager.GetLastResourceResolution();
+ EXPECT_EQ("", result);
+}
+
+TEST_F(AssetManager2Test, GetLastPathWithSingleApkAssets) {
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+ desired_config.language[0] = 'd';
+ desired_config.language[1] = 'e';
+
+ AssetManager2 assetmanager;
+ assetmanager.SetResourceResolutionLoggingEnabled(true);
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ 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);
+}
+
+TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) {
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+ desired_config.language[0] = 'd';
+ desired_config.language[1] = 'e';
+
+ AssetManager2 assetmanager;
+ assetmanager.SetResourceResolutionLoggingEnabled(true);
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get(), basic_de_fr_assets_.get()});
+
+ Res_value value = Res_value();
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ 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);
+}
+
+TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+
+ AssetManager2 assetmanager;
+ assetmanager.SetResourceResolutionLoggingEnabled(true);
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ Res_value value = Res_value();
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ auto resultEnabled = assetmanager.GetLastResourceResolution();
+ ASSERT_NE("", resultEnabled);
+
+ assetmanager.SetResourceResolutionLoggingEnabled(false);
+
+ auto resultDisabled = assetmanager.GetLastResourceResolution();
+ EXPECT_EQ("", resultDisabled);
+}
+
} // namespace android