From b3ae42e9762a826b1591ab932a6a092ad3e6c3a7 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Tue, 16 Oct 2018 12:48:38 -0700 Subject: [PATCH] Copy common resources between styles The docuemntation of setTo states that the resources two styles from different AssetManagers have in common will be set in the destination theme. This change adds this functionality. The package ids of the attributes, the package ids of reference values, and the cookie of attribute values have to be rewritten to match the destination AssetManager. This change can later be made more generic if rewriting references between packages is needed elsewhere. Bug: 115897657 Test: libandroidfw_tests and manual test of app specified in the bug Change-Id: Iee999ea2cc8473168cac11aaf3c34e14c958e5ae --- .../android/content/res/AssetManager.java | 14 +- .../android/content/res/ResourcesImpl.java | 2 +- core/jni/android_util_AssetManager.cpp | 25 +- libs/androidfw/AssetManager2.cpp | 255 ++++++++++++++++-- .../include/androidfw/AssetManager2.h | 12 +- libs/androidfw/tests/Theme_test.cpp | 39 ++- libs/androidfw/tests/data/lib_two/R.h | 12 + libs/androidfw/tests/data/lib_two/lib_two.apk | Bin 1106 -> 1426 bytes .../tests/data/lib_two/res/values/values.xml | 8 + 9 files changed, 319 insertions(+), 48 deletions(-) diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 0350effd84b8e..5f23749957753 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -1064,6 +1064,17 @@ public final class AssetManager implements AutoCloseable { } } + @UnsupportedAppUsage + void setThemeTo(long dstThemePtr, @NonNull AssetManager srcAssetManager, long srcThemePtr) { + synchronized (this) { + ensureValidLocked(); + synchronized (srcAssetManager) { + srcAssetManager.ensureValidLocked(); + nativeThemeCopy(mObject, dstThemePtr, srcAssetManager.mObject, srcThemePtr); + } + } + } + @Override protected void finalize() throws Throwable { if (DEBUG_REFS && mNumRefs != 0) { @@ -1375,7 +1386,8 @@ public final class AssetManager implements AutoCloseable { private static native void nativeThemeDestroy(long themePtr); private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId, boolean force); - static native void nativeThemeCopy(long destThemePtr, long sourceThemePtr); + private static native void nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr, + long srcAssetManagerPtr, long srcThemePtr); static native void nativeThemeClear(long themePtr); private static native int nativeThemeGetAttributeValue(long ptr, long themePtr, @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve); diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index dfa30a22597ee..0f5546b79fef9 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -1335,7 +1335,7 @@ public class ResourcesImpl { void setTo(ThemeImpl other) { synchronized (mKey) { synchronized (other.mKey) { - AssetManager.nativeThemeCopy(mTheme, other.mTheme); + mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme); mThemeResId = other.mThemeResId; mKey.setTo(other.getKey()); diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 830ca832fb95f..b2d44e73d8619 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -1208,13 +1208,23 @@ static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlon // jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); } -static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_theme_ptr, - jlong src_theme_ptr) { +static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_asset_manager_ptr, + jlong dst_theme_ptr, jlong src_asset_manager_ptr, jlong src_theme_ptr) { Theme* dst_theme = reinterpret_cast(dst_theme_ptr); Theme* src_theme = reinterpret_cast(src_theme_ptr); - if (!dst_theme->SetTo(*src_theme)) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "Themes are from different AssetManagers"); + + if (dst_asset_manager_ptr != src_asset_manager_ptr) { + ScopedLock dst_assetmanager(AssetManagerFromLong(dst_asset_manager_ptr)); + CHECK(dst_theme->GetAssetManager() == &(*dst_assetmanager)); + (void) dst_assetmanager; + + ScopedLock src_assetmanager(AssetManagerFromLong(src_asset_manager_ptr)); + CHECK(src_theme->GetAssetManager() == &(*src_assetmanager)); + (void) src_assetmanager; + + dst_theme->SetTo(*src_theme); + } else { + dst_theme->SetTo(*src_theme); } } @@ -1255,10 +1265,11 @@ static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong Theme* theme = reinterpret_cast(theme_ptr); CHECK(theme->GetAssetManager() == &(*assetmanager)); (void) assetmanager; - (void) theme; (void) priority; (void) tag; (void) prefix; + + theme->Dump(); } static jint NativeThemeGetChangingConfigurations(JNIEnv* /*env*/, jclass /*clazz*/, @@ -1377,7 +1388,7 @@ static const JNINativeMethod gAssetManagerMethods[] = { {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate}, {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy}, {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle}, - {"nativeThemeCopy", "(JJ)V", (void*)NativeThemeCopy}, + {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy}, {"nativeThemeClear", "(J)V", (void*)NativeThemeClear}, {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I", (void*)NativeThemeGetAttributeValue}, diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 04cc5bb30ade1..288ba32c47a6f 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "android-base/logging.h" #include "android-base/stringprintf.h" @@ -869,6 +870,17 @@ void AssetManager2::InvalidateCaches(uint32_t diff) { } } +uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) { + for (auto& package_group : package_groups_) { + for (auto& package2 : package_group.packages_) { + if (package2.loaded_package_ == package) { + return package_group.dynamic_ref_table.mAssignedPackageId; + } + } + } + return 0; +} + std::unique_ptr AssetManager2::NewTheme() { return std::unique_ptr(new Theme(this)); } @@ -1054,44 +1066,231 @@ void Theme::Clear() { } } -bool Theme::SetTo(const Theme& o) { +void Theme::SetTo(const Theme& o) { if (this == &o) { - return true; + return; } type_spec_flags_ = o.type_spec_flags_; - const bool copy_only_system = asset_manager_ != o.asset_manager_; - - for (size_t p = 0; p < packages_.size(); p++) { - const Package* package = o.packages_[p].get(); - if (package == nullptr || (copy_only_system && p != 0x01)) { - // The other theme doesn't have this package, clear ours. - packages_[p].reset(); - continue; - } - - if (packages_[p] == nullptr) { - // The other theme has this package, but we don't. Make one. - packages_[p].reset(new Package()); - } - - for (size_t t = 0; t < package->types.size(); t++) { - const ThemeType* type = package->types[t].get(); - if (type == nullptr) { - // The other theme doesn't have this type, clear ours. - packages_[p]->types[t].reset(); + if (asset_manager_ == o.asset_manager_) { + // The theme comes from the same asset manager so all theme data can be copied exactly + for (size_t p = 0; p < packages_.size(); p++) { + const Package *package = o.packages_[p].get(); + if (package == nullptr) { + // The other theme doesn't have this package, clear ours. + packages_[p].reset(); continue; } - // Create a new type and update it to theirs. - const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry)); - void* copied_data = malloc(type_alloc_size); - memcpy(copied_data, type, type_alloc_size); - packages_[p]->types[t].reset(reinterpret_cast(copied_data)); + if (packages_[p] == nullptr) { + // The other theme has this package, but we don't. Make one. + packages_[p].reset(new Package()); + } + + for (size_t t = 0; t < package->types.size(); t++) { + const ThemeType *type = package->types[t].get(); + if (type == nullptr) { + // The other theme doesn't have this type, clear ours. + packages_[p]->types[t].reset(); + continue; + } + + // Create a new type and update it to theirs. + const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry)); + void *copied_data = malloc(type_alloc_size); + memcpy(copied_data, type, type_alloc_size); + packages_[p]->types[t].reset(reinterpret_cast(copied_data)); + } + } + } else { + std::map src_to_dest_asset_cookies; + typedef std::map SourceToDestinationRuntimePackageMap; + std::map src_asset_cookie_id_map; + + // Determine which ApkAssets are loaded in both theme AssetManagers + std::vector src_assets = o.asset_manager_->GetApkAssets(); + for (size_t i = 0; i < src_assets.size(); i++) { + const ApkAssets* src_asset = src_assets[i]; + + std::vector dest_assets = asset_manager_->GetApkAssets(); + for (size_t j = 0; j < dest_assets.size(); j++) { + const ApkAssets* dest_asset = dest_assets[j]; + + // Map the runtime package of the source apk asset to the destination apk asset + if (src_asset->GetPath() == dest_asset->GetPath()) { + const std::vector>& src_packages = + src_asset->GetLoadedArsc()->GetPackages(); + const std::vector>& dest_packages = + dest_asset->GetLoadedArsc()->GetPackages(); + + SourceToDestinationRuntimePackageMap package_map; + + // The source and destination package should have the same number of packages loaded in + // the same order. + const size_t N = src_packages.size(); + CHECK(N == dest_packages.size()) + << " LoadedArsc " << src_asset->GetPath() << " differs number of packages."; + for (size_t p = 0; p < N; p++) { + auto& src_package = src_packages[p]; + auto& dest_package = dest_packages[p]; + CHECK(src_package->GetPackageName() == dest_package->GetPackageName()) + << " Package " << src_package->GetPackageName() << " differs in load order."; + + int src_package_id = o.asset_manager_->GetAssignedPackageId(src_package.get()); + int dest_package_id = asset_manager_->GetAssignedPackageId(dest_package.get()); + package_map[src_package_id] = dest_package_id; + } + + src_to_dest_asset_cookies.insert(std::pair(i, j)); + src_asset_cookie_id_map.insert( + std::pair(i, package_map)); + break; + } + } + } + + // Reset the data in the destination theme + for (size_t p = 0; p < packages_.size(); p++) { + if (packages_[p] != nullptr) { + packages_[p].reset(); + } + } + + for (size_t p = 0; p < packages_.size(); p++) { + const Package *package = o.packages_[p].get(); + if (package == nullptr) { + continue; + } + + for (size_t t = 0; t < package->types.size(); t++) { + const ThemeType *type = package->types[t].get(); + if (type == nullptr) { + continue; + } + + for (size_t e = 0; e < type->entry_count; e++) { + const ThemeEntry &entry = type->entries[e]; + if (entry.value.dataType == Res_value::TYPE_NULL && + entry.value.data != Res_value::DATA_NULL_EMPTY) { + continue; + } + + // The package id of the attribute needs to be rewritten to the package id of the value in + // the destination + int attribute_dest_package_id = p; + if (attribute_dest_package_id != 0x01) { + // Find the cookie of the attribute resource id + FindEntryResult attribute_entry_result; + ApkAssetsCookie attribute_cookie = + o.asset_manager_->FindEntry(make_resid(p, t, e), 0 /* density_override */ , false, + &attribute_entry_result); + + // Determine the package id of the attribute in the destination AssetManager + auto attribute_package_map = src_asset_cookie_id_map.find(attribute_cookie); + if (attribute_package_map == src_asset_cookie_id_map.end()) { + continue; + } + auto attribute_dest_package = attribute_package_map->second.find( + attribute_dest_package_id); + if (attribute_dest_package == attribute_package_map->second.end()) { + continue; + } + attribute_dest_package_id = attribute_dest_package->second; + } + + // If the attribute value represents an attribute or reference, the package id of the + // value needs to be rewritten to the package id of the value in the destination + uint32_t attribue_data = entry.value.data; + if (entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE + || entry.value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE + || entry.value.dataType == Res_value::TYPE_ATTRIBUTE + || entry.value.dataType == Res_value::TYPE_REFERENCE) { + + // Determine the package id of the reference in the destination AssetManager + auto value_package_map = src_asset_cookie_id_map.find(entry.cookie); + if (value_package_map == src_asset_cookie_id_map.end()) { + continue; + } + + auto value_dest_package = value_package_map->second.find( + get_package_id(entry.value.data)); + if (value_dest_package == value_package_map->second.end()) { + continue; + } + + attribue_data = fix_package_id(entry.value.data, value_dest_package->second); + } + + // Lazily instantiate the destination package + std::unique_ptr& dest_package = packages_[attribute_dest_package_id]; + if (dest_package == nullptr) { + dest_package.reset(new Package()); + } + + // Lazily instantiate and resize the destination type + util::unique_cptr& dest_type = dest_package->types[t]; + if (dest_type == nullptr || dest_type->entry_count < type->entry_count) { + const size_t type_alloc_size = sizeof(ThemeType) + + (type->entry_count * sizeof(ThemeEntry)); + void* dest_data = malloc(type_alloc_size); + memset(dest_data, 0, type->entry_count * sizeof(ThemeEntry)); + + // Copy the existing destination type values if the type is resized + if (dest_type != nullptr) { + memcpy(dest_data, type, sizeof(ThemeType) + + (dest_type->entry_count * sizeof(ThemeEntry))); + } + + dest_type.reset(reinterpret_cast(dest_data)); + dest_type->entry_count = type->entry_count; + } + + // Find the cookie of the value in the destination + auto value_dest_cookie = src_to_dest_asset_cookies.find(entry.cookie); + if (value_dest_cookie == src_to_dest_asset_cookies.end()) { + continue; + } + + dest_type->entries[e].cookie = value_dest_cookie->second; + dest_type->entries[e].value.dataType = entry.value.dataType; + dest_type->entries[e].value.data = attribue_data; + dest_type->entries[e].type_spec_flags = entry.type_spec_flags; + } + } + } + } +} + +void Theme::Dump() const { + base::ScopedLogSeverity _log(base::INFO); + LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_); + + for (int p = 0; p < packages_.size(); p++) { + auto& package = packages_[p]; + if (package == nullptr) { + continue; + } + + for (int t = 0; t < package->types.size(); t++) { + auto& type = package->types[t]; + if (type == nullptr) { + continue; + } + + for (int e = 0; e < type->entry_count; e++) { + auto& entry = type->entries[e]; + if (entry.value.dataType == Res_value::TYPE_NULL && + entry.value.data != Res_value::DATA_NULL_EMPTY) { + continue; + } + + LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)", + make_resid(p, t, e), entry.value.data, + entry.value.dataType, entry.cookie); + } } } - return true; } } // namespace android diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 2f0ee01639fe9..5312b062473af 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -74,6 +74,8 @@ struct FindEntryResult; // AssetManager2 is the main entry point for accessing assets and resources. // AssetManager2 provides caching of resources retrieved via the underlying ApkAssets. class AssetManager2 { + friend Theme; + public: struct ResourceName { const char* package = nullptr; @@ -285,6 +287,9 @@ class AssetManager2 { // been seen while traversing bag parents. const ResolvedBag* GetBag(uint32_t resid, std::vector& child_resids); + // Retrieve the assigned package id of the package if loaded into this AssetManager + uint8_t GetAssignedPackageId(const LoadedPackage* package); + // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must // have a longer lifetime. std::vector apk_assets_; @@ -355,11 +360,14 @@ class Theme { bool ApplyStyle(uint32_t resid, bool force = false); // Sets this Theme to be a copy of `o` if `o` has the same AssetManager as this Theme. - // Returns false if the AssetManagers of the Themes were not compatible. - bool SetTo(const Theme& o); + // If `o` does not have the same AssetManager as this theme, only attributes from ApkAssets loaded + // into both AssetManagers will be copied to this theme. + void SetTo(const Theme& o); void Clear(); + void Dump() const; + inline const AssetManager2* GetAssetManager() const { return asset_manager_; } diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index 55d53edf6a2bd..2c39ceead123a 100644 --- a/libs/androidfw/tests/Theme_test.cpp +++ b/libs/androidfw/tests/Theme_test.cpp @@ -21,12 +21,14 @@ #include "TestHelpers.h" #include "androidfw/ResourceUtils.h" #include "data/lib_one/R.h" +#include "data/lib_two/R.h" #include "data/libclient/R.h" #include "data/styles/R.h" #include "data/system/R.h" namespace app = com::android::app; namespace lib_one = com::android::lib_one; +namespace lib_two = com::android::lib_two; namespace libclient = com::android::libclient; namespace android { @@ -263,7 +265,7 @@ TEST_F(ThemeTest, CopyThemeSameAssetManager) { ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleThree)); // Copy the theme to theme_one. - ASSERT_TRUE(theme_one->SetTo(*theme_two)); + theme_one->SetTo(*theme_two); // Clear theme_two to make sure we test that there WAS a copy. theme_two->Clear(); @@ -279,12 +281,14 @@ TEST_F(ThemeTest, CopyThemeSameAssetManager) { EXPECT_EQ(static_cast(ResTable_typeSpec::SPEC_PUBLIC), flags); } -TEST_F(ThemeTest, OnlyCopySystemThemeWhenAssetManagersDiffer) { +TEST_F(ThemeTest, OnlyCopySameAssetsThemeWhenAssetManagersDiffer) { AssetManager2 assetmanager_one; - assetmanager_one.SetApkAssets({system_assets_.get(), style_assets_.get()}); + assetmanager_one.SetApkAssets({system_assets_.get(), lib_one_assets_.get(), style_assets_.get(), + libclient_assets_.get()}); AssetManager2 assetmanager_two; - assetmanager_two.SetApkAssets({system_assets_.get(), style_assets_.get()}); + assetmanager_two.SetApkAssets({system_assets_.get(), lib_two_assets_.get(), lib_one_assets_.get(), + style_assets_.get()}); auto theme_one = assetmanager_one.NewTheme(); ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne)); @@ -292,17 +296,34 @@ TEST_F(ThemeTest, OnlyCopySystemThemeWhenAssetManagersDiffer) { auto theme_two = assetmanager_two.NewTheme(); ASSERT_TRUE(theme_two->ApplyStyle(R::style::Theme_One)); ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleTwo)); + ASSERT_TRUE(theme_two->ApplyStyle(fix_package_id(lib_one::R::style::Theme, 0x03), + false /*force*/)); + ASSERT_TRUE(theme_two->ApplyStyle(fix_package_id(lib_two::R::style::Theme, 0x02), + false /*force*/)); - EXPECT_TRUE(theme_one->SetTo(*theme_two)); + theme_one->SetTo(*theme_two); Res_value value; uint32_t flags; - // No app resources. - EXPECT_EQ(kInvalidCookie, theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags)); + // System resources (present in destination asset manager) + EXPECT_EQ(0, theme_one->GetAttribute(R::attr::foreground, &value, &flags)); - // Only system. - EXPECT_NE(kInvalidCookie, theme_one->GetAttribute(R::attr::foreground, &value, &flags)); + // The cookie of the style asset is 3 in the source and 2 in the destination. + // Check that the cookie has been rewritten to the destination values + EXPECT_EQ(2, theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags)); + + // The cookie of the lib_one asset is 2 in the source and 1 in the destination. + // The package id of the lib_one package is 0x03 in the source and 0x02 in the destination + // Check that the cookie and packages have been rewritten to the destination values + EXPECT_EQ(1, theme_one->GetAttribute(fix_package_id(lib_one::R::attr::attr1, 0x02), &value, + &flags)); + EXPECT_EQ(1, theme_one->GetAttribute(fix_package_id(lib_one::R::attr::attr2, 0x02), &value, + &flags)); + + // attr2 references an attribute in lib_one. Check that the resolution of the attribute value is + // correct after the value of attr2 had its package id rewritten to the destination package id + EXPECT_EQ(700, value.data); } } // namespace android diff --git a/libs/androidfw/tests/data/lib_two/R.h b/libs/androidfw/tests/data/lib_two/R.h index c04a9d3b4de01..92b9cc10e7a87 100644 --- a/libs/androidfw/tests/data/lib_two/R.h +++ b/libs/androidfw/tests/data/lib_two/R.h @@ -24,12 +24,24 @@ namespace android { namespace lib_two { struct R { + struct attr { + enum : uint32_t { + attr3 = 0x02010000, // default + }; + }; + struct string { enum : uint32_t { LibraryString = 0x02020000, // default foo = 0x02020001, // default }; }; + + struct style { + enum : uint32_t { + Theme = 0x02030000, // default + }; + }; }; } // namespace lib_two diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk index ad44f9c21e31dd0ae437cb11c62e3fddbc0606b2..486c23000276df564f5e0cbdaa4c3b569db41284 100644 GIT binary patch delta 473 zcmcb_F^QWez?+#xgn@~HgMp!8BF`!HhWU&tSGAvVurM%yFdqX0Ls4pRerZv1YO!8o zQE@U80}oJU;@>)^4(83Zj9iSS%s@3Ij0_AwWP!wCKm*1A(mX&cGkFJ- zygMsHF+&MMB|{EFDp($*vjE6~nFJEl0n0!LkZVAEGazPVNCfIFVJKoSp8SJJ+ng0> zX9m!mT(DV8OacrdKvh6zfiaMBU}Opbsz-=LfCMLRWOSAkKyoWc2M{m<14MvjvH`QK za0o*UP=E=FV}LYRm#+Yj#Q_ut=>Vz*dl$q9sRZ#s;xKoBB%vn5-2oGWyF&})4p^u` lR5OB15>NmN26!_vi8I5ZYVuiT2VRu8nk>a)#P%N?asWvAG0Xq} delta 285 zcmbQleTjo7z?+#xgn@~HgMnejM4nUXGv+h$*q>Z_f{B3vg!vd47>ZJh^Gl18Q;YQy zi;9z(7_F*W2>H_Hu0b+~E8yT&QK&m*PrU5;} m4CI3OAe|sShz{^(WD;kFd3Q1|s{=2Je + + + Hi from library two Foo from lib_two + + +