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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 <extra>} tags from
|
||||
|
||||
@@ -298,6 +298,13 @@ public class ResourcesImpl {
|
||||
+ Integer.toHexString(resid));
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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 @@ 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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user