Merge "Copy common resources between styles"

This commit is contained in:
Ryan Mitchell
2018-11-05 23:56:39 +00:00
committed by Android (Google) Code Review
9 changed files with 319 additions and 48 deletions

View File

@@ -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);

View File

@@ -1336,7 +1336,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());

View File

@@ -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<Theme*>(dst_theme_ptr);
Theme* src_theme = reinterpret_cast<Theme*>(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<AssetManager2> dst_assetmanager(AssetManagerFromLong(dst_asset_manager_ptr));
CHECK(dst_theme->GetAssetManager() == &(*dst_assetmanager));
(void) dst_assetmanager;
ScopedLock <AssetManager2> 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*>(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},

View File

@@ -21,6 +21,7 @@
#include <algorithm>
#include <iterator>
#include <set>
#include <map>
#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<Theme> AssetManager2::NewTheme() {
return std::unique_ptr<Theme>(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<ThemeType*>(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<ThemeType *>(copied_data));
}
}
} else {
std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
// Determine which ApkAssets are loaded in both theme AssetManagers
std::vector<const ApkAssets*> 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<const ApkAssets*> 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<std::unique_ptr<const LoadedPackage>>& src_packages =
src_asset->GetLoadedArsc()->GetPackages();
const std::vector<std::unique_ptr<const LoadedPackage>>& 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<ApkAssetsCookie, ApkAssetsCookie>(i, j));
src_asset_cookie_id_map.insert(
std::pair<ApkAssetsCookie, SourceToDestinationRuntimePackageMap>(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<Package>& 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<ThemeType>& 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<ThemeType *>(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

View File

@@ -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<uint32_t>& 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<const ApkAssets*> 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_;
}

View File

@@ -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<uint32_t>(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

View File

@@ -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

View File

@@ -15,9 +15,17 @@
-->
<resources>
<public type="attr" name="attr3" id="0x00010000" />
<attr name="attr3" format="integer" />
<public type="string" name="LibraryString" id="0x00020000" />
<string name="LibraryString">Hi from library two</string>
<public type="string" name="foo" id="0x00020001" />
<string name="foo">Foo from lib_two</string>
<public type="style" name="Theme" id="0x02030000" />
<style name="Theme">
<item name="com.android.lib_two:attr3">800</item>
</style>
</resources>