From 00964e9aea00c2e2319192ec45734f60a2117549 Mon Sep 17 00:00:00 2001 From: Felka Chang Date: Fri, 10 Dec 2021 01:19:08 +0800 Subject: [PATCH] Dump theme inheritance information in UnsupportedOperationException It isn't enough to only dump the theme id and name because there is no whole theme inheritance map. Developers need the map to figure out why the attribute value is so strange. A theme is a kind of style. This patch creates an API to retrieve the parent id for any style. And, it dumps the whole inheritance between themes until it can't find the parent style. Test: manual test described in b/209708201#comment5 Bug: 209708201 Change-Id: I9de0e6e9aaeff7cb9fc89dc9b8a04925e84f975c --- .../android/content/res/AssetManager.java | 17 +++++++++ core/java/android/content/res/Resources.java | 36 +++++++++++++++++-- .../android/content/res/ResourcesImpl.java | 8 +++++ core/jni/android_util_AssetManager.cpp | 8 +++++ libs/androidfw/AssetManager2.cpp | 21 +++++++++++ .../include/androidfw/AssetManager2.h | 6 ++++ 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 24c6a5a121781..bfd9fd0a4ef9c 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -791,6 +791,21 @@ public final class AssetManager implements AutoCloseable { } } + /** + * To get the parent theme resource id according to the parameter theme resource id. + * @param resId theme resource id. + * @return the parent theme resource id. + * @hide + */ + @StyleRes + int getParentThemeIdentifier(@StyleRes int resId) { + synchronized (this) { + ensureValidLocked(); + // name is checked in JNI. + return nativeGetParentThemeIdentifier(mObject, resId); + } + } + /** * 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. @@ -1600,6 +1615,8 @@ public final class AssetManager implements AutoCloseable { private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag, String prefix); static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr); + @StyleRes + private static native int nativeGetParentThemeIdentifier(long ptr, @StyleRes int styleId); // AssetInputStream related native methods. private static native void nativeAssetDestroy(long assetPtr); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index b5d4f09144ee5..cb53a2a7a7b8e 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1508,6 +1508,12 @@ public class Resources { * retrieve XML attributes with style and theme information applied. */ public final class Theme { + /** + * To trace parent themes needs to prevent a cycle situation. + * e.x. A's parent is B, B's parent is C, and C's parent is A. + */ + private static final int MAX_NUMBER_OF_TRACING_PARENT_THEME = 100; + private final Object mLock = new Object(); @GuardedBy("mLock") @@ -1801,6 +1807,13 @@ public class Resources { } } + @StyleRes + /*package*/ int getParentThemeIdentifier(@StyleRes int resId) { + synchronized (mLock) { + return mThemeImpl.getParentThemeIdentifier(resId); + } + } + /** * @hide */ @@ -1948,8 +1961,27 @@ public class Resources { public String toString() { final StringBuilder sb = new StringBuilder(); sb.append('{'); - sb.append("id=0x").append(Integer.toHexString(getAppliedStyleResId())).append(", "); - sb.append("themes=").append(Arrays.deepToString(getTheme())); + int themeResId = getAppliedStyleResId(); + int i = 0; + sb.append("InheritanceMap=["); + while (themeResId > 0) { + if (i > MAX_NUMBER_OF_TRACING_PARENT_THEME) { + sb.append(",..."); + break; + } + + if (i > 0) { + sb.append(", "); + } + sb.append("id=0x").append(Integer.toHexString(themeResId)); + sb.append(getResourcePackageName(themeResId)) + .append(":").append(getResourceTypeName(themeResId)) + .append("/").append(getResourceEntryName(themeResId)); + + i++; + themeResId = getParentThemeIdentifier(themeResId); + } + sb.append("], Themes=").append(Arrays.deepToString(getTheme())); sb.append('}'); return sb.toString(); } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index b9f93b85f0bf0..4d850b0ccfd5e 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -1310,6 +1310,14 @@ public class ResourcesImpl { return mThemeResId; } + @StyleRes + /*package*/ int getParentThemeIdentifier(@StyleRes int resId) { + if (resId > 0) { + return mAssets.getParentThemeIdentifier(resId); + } + return 0; + } + void applyStyle(int resId, boolean force) { mAssets.applyStyleToTheme(mTheme, resId, force); mThemeResId = resId; diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 86d781033e5e3..8c23b214b8fe0 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -881,6 +881,12 @@ static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin return static_cast(bag->entry_count); } +static jint NativeGetParentThemeIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock assetmanager(AssetManagerFromLong(ptr)); + const auto parentThemeResId = assetmanager->GetParentThemeResourceId(resid); + return parentThemeResId.value_or(0); +} + static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name, jstring def_type, jstring def_package) { ScopedUtfChars name_utf8(env, name); @@ -1464,6 +1470,8 @@ static const JNINativeMethod gAssetManagerMethods[] = { {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray}, {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize}, {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray}, + {"nativeGetParentThemeIdentifier", "(JI)I", + (void*)NativeGetParentThemeIdentifier}, // AssetManager resource name/ID methods. {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 22904a0528d57..136fc6ca4e2a3 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -902,6 +902,27 @@ std::string AssetManager2::GetLastResourceResolution() const { return log_stream.str(); } +base::expected AssetManager2::GetParentThemeResourceId(uint32_t resid) +const { + auto entry = FindEntry(resid, 0u /* density_override */, + false /* stop_at_first_match */, + false /* ignore_configuration */); + if (!entry.has_value()) { + return base::unexpected(entry.error()); + } + + auto entry_map = std::get_if>(&entry->entry); + if (entry_map == nullptr) { + // Not a bag, nothing to do. + return base::unexpected(std::nullopt); + } + + auto map = *entry_map; + const uint32_t parent_resid = dtohl(map->parent.ident); + + return parent_resid; +} + base::expected AssetManager2::GetResourceName( uint32_t resid) const { auto result = FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index a3b42df12da90..1bde792da2baf 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -192,6 +192,12 @@ class AssetManager2 { std::unique_ptr OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie, Asset::AccessMode mode) const; + // Returns the resource id of parent style of the specified theme. + // + // Returns a null error if the name is missing/corrupt, or an I/O error if reading resource data + // failed. + base::expected GetParentThemeResourceId(uint32_t resid) const; + // Returns the resource name of the specified resource ID. // // Utf8 strings are preferred, and only if they are unavailable are the Utf16 variants populated.