Surface <overlayable> info in Java AssetManager
Add a new, hidden method to AssetManager to extract a mapping overlayable name -> overlayable actor for all <overlayable> blocks in a package. [This will eventually be used to check if the caller of the OMS AIDL API is the registered actor for a given overlay.] Also, teach AssetManager2 to not accept packages that re-use the same overlayable name. [Such packages have always been ill-formed.] Bug: 123894537 Test: make libandroidfw_tests Change-Id: I1117fd3503f04fe4c73eb7114901e022508f4d9e
This commit is contained in:
parent
23f34cd61f
commit
c92c4dd6c6
@ -47,6 +47,7 @@ import java.nio.channels.FileLock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides access to an application's raw asset files; see {@link Resources}
|
||||
@ -1345,6 +1346,17 @@ public final class AssetManager implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
public @Nullable Map<String, String> getOverlayableMap(String packageName) {
|
||||
synchronized (this) {
|
||||
ensureValidLocked();
|
||||
return nativeGetOverlayableMap(mObject, packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void incRefsLocked(long id) {
|
||||
if (DEBUG_REFS) {
|
||||
@ -1462,6 +1474,8 @@ public final class AssetManager implements AutoCloseable {
|
||||
|
||||
private static native void nativeVerifySystemIdmaps();
|
||||
private static native String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
|
||||
private static native @Nullable Map nativeGetOverlayableMap(long ptr,
|
||||
@NonNull String packageName);
|
||||
|
||||
// Global debug native methods.
|
||||
/**
|
||||
|
@ -104,6 +104,12 @@ static struct configuration_offsets_t {
|
||||
jfieldID mScreenHeightDpOffset;
|
||||
} gConfigurationOffsets;
|
||||
|
||||
static struct arraymap_offsets_t {
|
||||
jclass classObject;
|
||||
jmethodID constructor;
|
||||
jmethodID put;
|
||||
} gArrayMapOffsets;
|
||||
|
||||
jclass g_stringClass = nullptr;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -326,6 +332,50 @@ static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
|
||||
return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
|
||||
}
|
||||
|
||||
static jobject NativeGetOverlayableMap(JNIEnv* env, jclass /*clazz*/, jlong ptr,
|
||||
jstring package_name) {
|
||||
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
|
||||
const ScopedUtfChars package_name_utf8(env, package_name);
|
||||
CHECK(package_name_utf8.c_str() != nullptr);
|
||||
const std::string std_package_name(package_name_utf8.c_str());
|
||||
const std::unordered_map<std::string, std::string>* map = nullptr;
|
||||
|
||||
assetmanager->ForEachPackage([&](const std::string& this_package_name, uint8_t package_id) {
|
||||
if (this_package_name == std_package_name) {
|
||||
map = assetmanager->GetOverlayableMapForPackage(package_id);
|
||||
}
|
||||
});
|
||||
|
||||
if (map == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jobject array_map = env->NewObject(gArrayMapOffsets.classObject, gArrayMapOffsets.constructor);
|
||||
if (array_map == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& iter : *map) {
|
||||
jstring name = env->NewStringUTF(iter.first.c_str());
|
||||
if (env->ExceptionCheck()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jstring actor = env->NewStringUTF(iter.second.c_str());
|
||||
if (env->ExceptionCheck()) {
|
||||
env->DeleteLocalRef(name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
env->CallObjectMethod(array_map, gArrayMapOffsets.put, name, actor);
|
||||
|
||||
env->DeleteLocalRef(name);
|
||||
env->DeleteLocalRef(actor);
|
||||
}
|
||||
|
||||
return array_map;
|
||||
}
|
||||
|
||||
static jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset,
|
||||
jlongArray out_offsets) {
|
||||
off64_t start_offset, length;
|
||||
@ -1524,6 +1574,8 @@ static const JNINativeMethod gAssetManagerMethods[] = {
|
||||
{"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps},
|
||||
{"nativeCreateIdmapsForStaticOverlaysTargetingAndroid", "()[Ljava/lang/String;",
|
||||
(void*)NativeCreateIdmapsForStaticOverlaysTargetingAndroid},
|
||||
{"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
|
||||
(void*)NativeGetOverlayableMap},
|
||||
|
||||
// Global management/debug methods.
|
||||
{"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
|
||||
@ -1575,6 +1627,14 @@ int register_android_content_AssetManager(JNIEnv* env) {
|
||||
gConfigurationOffsets.mScreenHeightDpOffset =
|
||||
GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
|
||||
|
||||
jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap");
|
||||
gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass);
|
||||
gArrayMapOffsets.constructor =
|
||||
GetMethodIDOrDie(env, gArrayMapOffsets.classObject, "<init>", "()V");
|
||||
gArrayMapOffsets.put =
|
||||
GetMethodIDOrDie(env, gArrayMapOffsets.classObject, "put",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
|
||||
return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods,
|
||||
NELEM(gAssetManagerMethods));
|
||||
}
|
||||
|
@ -203,6 +203,27 @@ const DynamicRefTable* AssetManager2::GetDynamicRefTableForCookie(ApkAssetsCooki
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::string>*
|
||||
AssetManager2::GetOverlayableMapForPackage(uint32_t package_id) const {
|
||||
|
||||
if (package_id >= package_ids_.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t idx = package_ids_[package_id];
|
||||
if (idx == 0xff) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const PackageGroup& package_group = package_groups_[idx];
|
||||
if (package_group.packages_.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto loaded_package = package_group.packages_[0].loaded_package_;
|
||||
return &loaded_package->GetOverlayableMap();
|
||||
}
|
||||
|
||||
void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
|
||||
const int diff = configuration_.diff(configuration);
|
||||
configuration_ = configuration;
|
||||
|
@ -598,6 +598,13 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
|
||||
std::string actor;
|
||||
util::ReadUtf16StringFromDevice(header->actor, arraysize(header->actor), &actor);
|
||||
|
||||
if (loaded_package->overlayable_map_.find(name) !=
|
||||
loaded_package->overlayable_map_.end()) {
|
||||
LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'.";
|
||||
return {};
|
||||
}
|
||||
loaded_package->overlayable_map_.emplace(name, actor);
|
||||
|
||||
// Iterate over the overlayable policy chunks contained within the overlayable chunk data
|
||||
ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
|
||||
while (overlayable_iter.HasNext()) {
|
||||
|
@ -124,6 +124,9 @@ class AssetManager2 {
|
||||
// This may be nullptr if the APK represented by `cookie` has no resource table.
|
||||
const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const;
|
||||
|
||||
const std::unordered_map<std::string, std::string>*
|
||||
GetOverlayableMapForPackage(uint32_t package_id) const;
|
||||
|
||||
// Sets/resets the configuration for this AssetManager. This will cause all
|
||||
// caches that are related to the configuration change to be invalidated.
|
||||
void SetConfiguration(const ResTable_config& configuration);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
@ -242,6 +243,10 @@ class LoadedPackage {
|
||||
return defines_overlayable_;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::string>& GetOverlayableMap() const {
|
||||
return overlayable_map_;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
|
||||
|
||||
@ -261,6 +266,7 @@ class LoadedPackage {
|
||||
ByteBucketArray<uint32_t> resource_ids_;
|
||||
std::vector<DynamicPackageEntry> dynamic_package_map_;
|
||||
std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
|
||||
std::unordered_map<std::string, std::string> overlayable_map_;
|
||||
};
|
||||
|
||||
// Read-only view into a resource table. This class validates all data
|
||||
|
@ -71,6 +71,9 @@ class AssetManager2Test : public ::testing::Test {
|
||||
|
||||
app_assets_ = ApkAssets::Load(GetTestDataPath() + "/app/app.apk");
|
||||
ASSERT_THAT(app_assets_, NotNull());
|
||||
|
||||
overlayable_assets_ = ApkAssets::Load(GetTestDataPath() + "/overlayable/overlayable.apk");
|
||||
ASSERT_THAT(overlayable_assets_, NotNull());
|
||||
}
|
||||
|
||||
protected:
|
||||
@ -83,6 +86,7 @@ class AssetManager2Test : public ::testing::Test {
|
||||
std::unique_ptr<const ApkAssets> appaslib_assets_;
|
||||
std::unique_ptr<const ApkAssets> system_assets_;
|
||||
std::unique_ptr<const ApkAssets> app_assets_;
|
||||
std::unique_ptr<const ApkAssets> overlayable_assets_;
|
||||
};
|
||||
|
||||
TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
|
||||
@ -703,4 +707,20 @@ TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
|
||||
EXPECT_EQ("", resultDisabled);
|
||||
}
|
||||
|
||||
TEST_F(AssetManager2Test, GetOverlayableMap) {
|
||||
ResTable_config desired_config;
|
||||
memset(&desired_config, 0, sizeof(desired_config));
|
||||
|
||||
AssetManager2 assetmanager;
|
||||
assetmanager.SetResourceResolutionLoggingEnabled(true);
|
||||
assetmanager.SetConfiguration(desired_config);
|
||||
assetmanager.SetApkAssets({overlayable_assets_.get()});
|
||||
|
||||
const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
|
||||
ASSERT_NE(nullptr, map);
|
||||
ASSERT_EQ(2, map->size());
|
||||
ASSERT_EQ(map->at("OverlayableResources1"), "overlay://theme");
|
||||
ASSERT_EQ(map->at("OverlayableResources2"), "overlay://com.android.overlayable");
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
@ -331,7 +331,7 @@ TEST(LoadedArscTest, ResourceIdentifierIterator) {
|
||||
|
||||
const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
|
||||
ASSERT_EQ(1u, packages.size());
|
||||
EXPECT_EQ(std::string("com.android.basic"), packages[0]->GetPackageName());
|
||||
ASSERT_EQ(std::string("com.android.basic"), packages[0]->GetPackageName());
|
||||
|
||||
const auto& loaded_package = packages[0];
|
||||
auto iter = loaded_package->begin();
|
||||
@ -369,6 +369,24 @@ TEST(LoadedArscTest, ResourceIdentifierIterator) {
|
||||
ASSERT_EQ(end, iter);
|
||||
}
|
||||
|
||||
TEST(LoadedArscTest, GetOverlayableMap) {
|
||||
std::string contents;
|
||||
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlayable/overlayable.apk",
|
||||
"resources.arsc", &contents));
|
||||
|
||||
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
|
||||
ASSERT_NE(nullptr, loaded_arsc);
|
||||
|
||||
const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
|
||||
ASSERT_EQ(1u, packages.size());
|
||||
ASSERT_EQ(std::string("com.android.overlayable"), packages[0]->GetPackageName());
|
||||
|
||||
const auto map = packages[0]->GetOverlayableMap();
|
||||
ASSERT_EQ(2, map.size());
|
||||
ASSERT_EQ(map.at("OverlayableResources1"), "overlay://theme");
|
||||
ASSERT_EQ(map.at("OverlayableResources2"), "overlay://com.android.overlayable");
|
||||
}
|
||||
|
||||
// structs with size fields (like Res_value, ResTable_entry) should be
|
||||
// backwards and forwards compatible (aka checking the size field against
|
||||
// sizeof(Res_value) might not be backwards compatible.
|
||||
|
Loading…
x
Reference in New Issue
Block a user