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:
Mårten Kongstad 2019-02-05 01:29:59 +01:00 committed by Todd Kennedy
parent 23f34cd61f
commit c92c4dd6c6
8 changed files with 150 additions and 1 deletions

View File

@ -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.
/**

View File

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

View File

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

View File

@ -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()) {

View File

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

View File

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

View File

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

View File

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