Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f3669b7 authored by Winson's avatar Winson
Browse files

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
parent 0c891e8f
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -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,
+19 −5
Original line number Diff line number Diff line
@@ -2027,6 +2027,20 @@ public class Resources {
        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
     * an XML file.  You call this when you are at the parent tag of the
+7 −0
Original line number Diff line number Diff line
@@ -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();
+27 −28
Original line number Diff line number Diff line
@@ -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},
+154 −53
Original line number Diff line number Diff line
@@ -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)) {
        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_chunk = filtered_group.types[i];
          const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
        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_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,9 +457,20 @@ 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)) {
        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 = 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);
@@ -456,7 +484,11 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri
        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,44 +510,113 @@ 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;
}

bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const {
  FindEntryResult entry;
  ApkAssetsCookie cookie =
      FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, &entry);
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) {
    return false;
    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) {
    return false;

  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;
    }

  out_name->package = package->GetPackageName().data();
  out_name->package_len = package->GetPackageName().size();
    if (!prefix.empty()) {
      log_stream << "\n\t" << prefix << ": " << *step.package_name;

  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;
      if (!step.config_name.isEmpty()) {
        log_stream << " -" << step.config_name;
      }
    }
  }

  return log_stream.str();
}

  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) {
bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const {
  FindEntryResult entry;
  ApkAssetsCookie cookie =
      FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, &entry);
  if (cookie == kInvalidCookie) {
    return false;
  }

  const LoadedPackage* package =
      apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
  if (package == 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 {
Loading