From c92c4dd6c6442d0876261ec3e634937f16edb3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= Date: Tue, 5 Feb 2019 01:29:59 +0100 Subject: [PATCH] Surface info in Java AssetManager Add a new, hidden method to AssetManager to extract a mapping overlayable name -> overlayable actor for all 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 --- .../android/content/res/AssetManager.java | 14 +++++ core/jni/android_util_AssetManager.cpp | 60 +++++++++++++++++++ libs/androidfw/AssetManager2.cpp | 21 +++++++ libs/androidfw/LoadedArsc.cpp | 7 +++ .../include/androidfw/AssetManager2.h | 3 + libs/androidfw/include/androidfw/LoadedArsc.h | 6 ++ libs/androidfw/tests/AssetManager2_test.cpp | 20 +++++++ libs/androidfw/tests/LoadedArsc_test.cpp | 20 ++++++- 8 files changed, 150 insertions(+), 1 deletion(-) diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index faf17e011b23..49b4cb01c6a6 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -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 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. /** diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index d493ddfaae13..a212f47c0104 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -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& AssetManagerFromLong(jlong ptr) { return *AssetManagerForNdkAssetManager(reinterpret_cast(ptr)); } +static jobject NativeGetOverlayableMap(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jstring package_name) { + ScopedLock 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* 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, 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, "", "()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)); } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 81afd937d85e..66d8542553d2 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -203,6 +203,27 @@ const DynamicRefTable* AssetManager2::GetDynamicRefTableForCookie(ApkAssetsCooki return nullptr; } +const std::unordered_map* + 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; diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index bdd47061054a..72873abc6a42 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -598,6 +598,13 @@ std::unique_ptr 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 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()) { diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index d862182d8960..fc5aa9c7f1b9 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -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* + 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); diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index b5f4006dbb00..950f5413f550 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "android-base/macros.h" @@ -242,6 +243,10 @@ class LoadedPackage { return defines_overlayable_; } + const std::unordered_map& GetOverlayableMap() const { + return overlayable_map_; + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); @@ -261,6 +266,7 @@ class LoadedPackage { ByteBucketArray resource_ids_; std::vector dynamic_package_map_; std::vector>> overlayable_infos_; + std::unordered_map overlayable_map_; }; // Read-only view into a resource table. This class validates all data diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 447fdf5d306a..40c8e46e4d84 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -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 appaslib_assets_; std::unique_ptr system_assets_; std::unique_ptr app_assets_; + std::unique_ptr 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 diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index b8d3c6bf92fb..d58e8d20c8aa 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -331,7 +331,7 @@ TEST(LoadedArscTest, ResourceIdentifierIterator) { const std::vector>& 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 loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_NE(nullptr, loaded_arsc); + + const std::vector>& 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.