summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWinson <chiuwinson@google.com>2019-01-11 11:28:34 -0800
committerWinson <chiuwinson@google.com>2019-01-14 14:52:00 -0800
commit2f3669b767129bc8739bb03e80abc65eb54a3471 (patch)
treeccb7294d22f43c64f863defb345a1383294f221b
parent0c891e8f4e5d7e1798e564cee80694264751aa9a (diff)
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
-rw-r--r--core/java/android/content/res/AssetManager.java34
-rw-r--r--core/java/android/content/res/Resources.java24
-rw-r--r--core/java/android/content/res/ResourcesImpl.java7
-rw-r--r--core/jni/android_util_AssetManager.cpp55
-rw-r--r--libs/androidfw/AssetManager2.cpp207
-rw-r--r--libs/androidfw/ResourceUtils.cpp61
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h50
-rw-r--r--libs/androidfw/include/androidfw/ResourceUtils.h12
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp107
9 files changed, 471 insertions, 86 deletions
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index f1a4db25cd14..9e0a9ba0dc7b 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -732,6 +732,38 @@ public final class AssetManager implements AutoCloseable {
}
}
+ /**
+ * 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 @@ public final class AssetManager implements AutoCloseable {
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 365ceac68ee1..c4b315ec90c8 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2010,22 +2010,36 @@ public class Resources {
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 &lt;extra&gt;} tags from
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 2ad4f625ef8c..77796d9ebdf5 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -299,6 +299,13 @@ public class ResourcesImpl {
}
@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 7b564ae162ce..4101c04162af 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 @@ static jstring NativeGetResourceName(JNIEnv* env, jclass /*clazz*/, jlong ptr, j
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 @@ static jstring NativeGetResourceEntryName(JNIEnv* env, jclass /*clazz*/, jlong p
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 @@ static const JNINativeMethod gAssetManagerMethods[] = {
{"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 ad9ec02648b1..3c35d9b33fc8 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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
// 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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
// 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;
+ }
+
+ // 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_chunk;
- best_config = &this_config;
- best_offset = offset;
+ 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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
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 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) cons
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 d63feb01ef83..645984d85c34 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -48,4 +48,65 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin
!(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 0d492984d41d..f29769b834d1 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -229,6 +229,14 @@ class AssetManager2 {
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 @@ class AssetManager2 {
// 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 d94779bf5225..eb6eb8e66175 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 @@ namespace android {
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 5449a54d08de..105dcd209bf7 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -586,4 +586,111 @@ TEST_F(AssetManager2Test, OpenDirFromManyApks) {
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