diff options
Diffstat (limited to 'libs/androidfw')
72 files changed, 3822 insertions, 1687 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 89c1080810e3..e39926beee41 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -14,15 +14,27 @@ // libandroidfw is partially built for the host (used by obbtool, aapt, and others) -cc_library { - name: "libandroidfw", - host_supported: true, +cc_defaults { + name: "libandroidfw_defaults", cflags: [ - "-Wall", "-Werror", - "-Wunused", "-Wunreachable-code", ], + target: { + windows: { + // The Windows compiler warns incorrectly for value initialization with {}. + cppflags: ["-Wno-missing-field-initializers"], + }, + host: { + cflags: ["-DSTATIC_ANDROIDFW_FOR_TOOLS"], + }, + }, +} + +cc_library { + name: "libandroidfw", + defaults: ["libandroidfw_defaults"], + host_supported: true, srcs: [ "ApkAssets.cpp", "Asset.cpp", @@ -31,6 +43,7 @@ cc_library { "AssetManager2.cpp", "AttributeResolution.cpp", "ChunkIterator.cpp", + "Idmap.cpp", "LoadedArsc.cpp", "LocaleData.cpp", "misc.cpp", @@ -68,7 +81,6 @@ cc_library { }, }, host: { - cflags: ["-DSTATIC_ANDROIDFW_FOR_TOOLS"], shared: { enabled: false, }, @@ -85,12 +97,87 @@ cc_library { }, windows: { enabled: true, - cppflags: ["-Wno-missing-field-initializers"], // The Windows compiler warns - // incorrectly for value - // initialization with {}. }, }, sanitize: { blacklist: "libandroidfw_blacklist.txt", }, } + +common_test_libs = [ + "libandroidfw", + "libbase", + "libcutils", + "libutils", + "libziparchive", +] + +cc_test { + name: "libandroidfw_tests", + host_supported: true, + defaults: ["libandroidfw_defaults"], + cppflags: [ + // This is to suppress warnings/errors from gtest + "-Wno-unnamed-type-template-args", + ], + srcs: [ + // Helpers/infra for testing. + "tests/CommonHelpers.cpp", + "tests/TestHelpers.cpp", + "tests/TestMain.cpp", + + // Actual tests. + "tests/ApkAssets_test.cpp", + "tests/AppAsLib_test.cpp", + "tests/Asset_test.cpp", + "tests/AssetManager2_test.cpp", + "tests/AttributeFinder_test.cpp", + "tests/AttributeResolution_test.cpp", + "tests/ByteBucketArray_test.cpp", + "tests/Config_test.cpp", + "tests/ConfigLocale_test.cpp", + "tests/Idmap_test.cpp", + "tests/LoadedArsc_test.cpp", + "tests/ResourceUtils_test.cpp", + "tests/ResTable_test.cpp", + "tests/Split_test.cpp", + "tests/StringPiece_test.cpp", + "tests/Theme_test.cpp", + "tests/TypeWrappers_test.cpp", + "tests/ZipUtils_test.cpp", + ], + static_libs: ["libgmock"], + target: { + android: { + srcs: [ + "tests/BackupData_test.cpp", + "tests/ObbFile_test.cpp", + ], + shared_libs: common_test_libs + ["libui"], + }, + host: { + static_libs: common_test_libs + ["liblog", "libz"], + }, + }, + data: ["tests/data/**/*.apk"], +} + +cc_benchmark { + name: "libandroidfw_benchmarks", + defaults: ["libandroidfw_defaults"], + srcs: [ + // Helpers/infra for benchmarking. + "tests/BenchMain.cpp", + "tests/BenchmarkHelpers.cpp", + "tests/CommonHelpers.cpp", + + // Actual benchmarks. + "tests/AssetManager2_bench.cpp", + "tests/AttributeResolution_bench.cpp", + "tests/SparseEntry_bench.cpp", + "tests/Theme_bench.cpp", + ], + shared_libs: common_test_libs, + data: ["tests/data/**/*.apk"], +} + diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index a5b1d29dbf91..8f58f74d4652 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -14,68 +14,142 @@ * limitations under the License. */ -#define ATRACE_TAG ATRACE_TAG_RESOURCES - #include "androidfw/ApkAssets.h" #include <algorithm> +#include "android-base/errors.h" +#include "android-base/file.h" #include "android-base/logging.h" +#include "android-base/unique_fd.h" +#include "android-base/utf8.h" +#include "utils/Compat.h" #include "utils/FileMap.h" -#include "utils/Trace.h" #include "ziparchive/zip_archive.h" #include "androidfw/Asset.h" +#include "androidfw/Idmap.h" +#include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" namespace android { +using base::SystemErrorCodeToString; +using base::unique_fd; + +static const std::string kResourcesArsc("resources.arsc"); + +ApkAssets::ApkAssets(void* unmanaged_handle, const std::string& path) + : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path) { +} + std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) { - return ApkAssets::LoadImpl(path, system, false /*load_as_shared_library*/); + return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/); } std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path, bool system) { - return ApkAssets::LoadImpl(path, system, true /*load_as_shared_library*/); + return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, true /*load_as_shared_library*/); +} + +std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, + bool system) { + std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path); + if (idmap_asset == nullptr) { + return {}; + } + + const StringPiece idmap_data( + reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)), + static_cast<size_t>(idmap_asset->getLength())); + std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data); + if (loaded_idmap == nullptr) { + LOG(ERROR) << "failed to load IDMAP " << idmap_path; + return {}; + } + return LoadImpl({} /*fd*/, loaded_idmap->OverlayApkPath(), std::move(idmap_asset), + std::move(loaded_idmap), system, false /*load_as_shared_library*/); } -std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(const std::string& path, bool system, - bool load_as_shared_library) { - ATRACE_CALL(); +std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd, + const std::string& friendly_name, + bool system, bool force_shared_lib) { + return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/, + system, force_shared_lib); +} + +std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) { + unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC)); + if (fd == -1) { + LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno); + return {}; + } + + const off64_t file_len = lseek64(fd, 0, SEEK_END); + if (file_len < 0) { + LOG(ERROR) << "Failed to get size of file '" << path << "': " << SystemErrorCodeToString(errno); + return {}; + } + + std::unique_ptr<FileMap> file_map = util::make_unique<FileMap>(); + if (!file_map->create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) { + LOG(ERROR) << "Failed to mmap file '" << path << "': " << SystemErrorCodeToString(errno); + return {}; + } + return Asset::createFromUncompressedMap(std::move(file_map), Asset::AccessMode::ACCESS_RANDOM); +} + +std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( + unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset, + std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) { ::ZipArchiveHandle unmanaged_handle; - int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle); + int32_t result; + if (fd >= 0) { + result = + ::OpenArchiveFd(fd.release(), path.c_str(), &unmanaged_handle, true /*assume_ownership*/); + } else { + result = ::OpenArchive(path.c_str(), &unmanaged_handle); + } + if (result != 0) { - LOG(ERROR) << ::ErrorCodeString(result); + LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result); return {}; } // Wrap the handle in a unique_ptr so it gets automatically closed. - std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets()); - loaded_apk->zip_handle_.reset(unmanaged_handle); + std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path)); - ::ZipString entry_name("resources.arsc"); + // Find the resource table. + ::ZipString entry_name(kResourcesArsc.c_str()); ::ZipEntry entry; result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry); if (result != 0) { - LOG(ERROR) << ::ErrorCodeString(result); - return {}; + // There is no resources.arsc, so create an empty LoadedArsc and return. + loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); + return std::move(loaded_apk); } if (entry.method == kCompressDeflated) { - LOG(WARNING) << "resources.arsc is compressed."; + LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed."; } - loaded_apk->path_ = path; - loaded_apk->resources_asset_ = - loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER); + // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open. + loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER); if (loaded_apk->resources_asset_ == nullptr) { + LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'."; return {}; } + // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid. + loaded_apk->idmap_asset_ = std::move(idmap_asset); + + const StringPiece data( + reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)), + loaded_apk->resources_asset_->getLength()); loaded_apk->loaded_arsc_ = - LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/), - loaded_apk->resources_asset_->getLength(), system, load_as_shared_library); + LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library); if (loaded_apk->loaded_arsc_ == nullptr) { + LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'."; return {}; } @@ -84,14 +158,12 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(const std::string& path, bo } std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const { - ATRACE_CALL(); CHECK(zip_handle_ != nullptr); ::ZipString name(path.c_str()); ::ZipEntry entry; int32_t result = ::FindEntry(zip_handle_.get(), name, &entry); if (result != 0) { - LOG(ERROR) << "No entry '" << path << "' found in APK '" << path_ << "'"; return {}; } @@ -153,12 +225,16 @@ bool ApkAssets::ForEachFile(const std::string& root_path, while ((result = ::Next(cookie, &entry, &name)) == 0) { StringPiece full_file_path(reinterpret_cast<const char*>(name.name), name.name_length); StringPiece leaf_file_path = full_file_path.substr(root_path_full.size()); - auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/'); - if (iter != leaf_file_path.end()) { - dirs.insert( - leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string()); - } else if (!leaf_file_path.empty()) { - f(leaf_file_path, kFileTypeRegular); + + if (!leaf_file_path.empty()) { + auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/'); + if (iter != leaf_file_path.end()) { + std::string dir = + leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string(); + dirs.insert(std::move(dir)); + } else { + f(leaf_file_path, kFileTypeRegular); + } } } ::EndIteration(cookie); diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index b4ccae758347..fc625bbaf72d 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -73,6 +73,7 @@ static volatile int32_t gCount = 0; const char* AssetManager::RESOURCES_FILENAME = "resources.arsc"; const char* AssetManager::IDMAP_BIN = "/system/bin/idmap"; const char* AssetManager::OVERLAY_DIR = "/vendor/overlay"; +const char* AssetManager::PRODUCT_OVERLAY_DIR = "/product/overlay"; const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme"; const char* AssetManager::TARGET_PACKAGE_NAME = "android"; const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk"; @@ -148,6 +149,18 @@ AssetManager::~AssetManager() { int count = android_atomic_dec(&gCount); if (kIsDebug) { ALOGI("Destroying AssetManager in %p #%d\n", this, count); + } else { + ALOGV("Destroying AssetManager in %p #%d\n", this, count); + } + + // Manually close any fd paths for which we have not yet opened their zip (which + // will take ownership of the fd and close it when done). + for (size_t i=0; i<mAssetPaths.size(); i++) { + ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd, + mAssetPaths[i].zip.get()); + if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) { + close(mAssetPaths[i].rawFd); + } } delete mConfig; @@ -194,7 +207,7 @@ bool AssetManager::addAssetPath( ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); ap.isSystemAsset = isSystemAsset; - mAssetPaths.add(ap); + ssize_t apPos = mAssetPaths.add(ap); // new paths are always added at the end if (cookie) { @@ -211,7 +224,7 @@ bool AssetManager::addAssetPath( #endif if (mResources != NULL) { - appendPathToResTable(ap, appAsLib); + appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib); } return true; @@ -280,7 +293,35 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie) } return true; - } +} + +bool AssetManager::addAssetFd( + int fd, const String8& debugPathName, int32_t* cookie, bool appAsLib, + bool assume_ownership) { + AutoMutex _l(mLock); + + asset_path ap; + + ap.path = debugPathName; + ap.rawFd = fd; + ap.type = kFileTypeRegular; + ap.assumeOwnership = assume_ownership; + + ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string()); + + ssize_t apPos = mAssetPaths.add(ap); + + // new paths are always added at the end + if (cookie) { + *cookie = static_cast<int32_t>(mAssetPaths.size()); + } + + if (mResources != NULL) { + appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib); + } + + return true; +} bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApkPath, uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize) @@ -406,7 +447,8 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode) i--; ALOGV("Looking for asset '%s' in '%s'\n", assetName.string(), mAssetPaths.itemAt(i).path.string()); - Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i)); + Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, + mAssetPaths.editItemAt(i)); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; } @@ -435,7 +477,7 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t i--; ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string()); Asset* pAsset = openNonAssetInPathLocked( - fileName, mode, mAssetPaths.itemAt(i)); + fileName, mode, mAssetPaths.editItemAt(i)); if (pAsset != NULL) { if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1); return pAsset != kExcludedAsset ? pAsset : NULL; @@ -457,7 +499,7 @@ Asset* AssetManager::openNonAsset(const int32_t cookie, const char* fileName, Ac ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(which).path.string()); Asset* pAsset = openNonAssetInPathLocked( - fileName, mode, mAssetPaths.itemAt(which)); + fileName, mode, mAssetPaths.editItemAt(which)); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; } @@ -491,7 +533,7 @@ FileType AssetManager::getFileType(const char* fileName) } } -bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const { +bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const { // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; @@ -505,7 +547,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) con Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources->getTableCount(); ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); - if (ap.type != kFileTypeDirectory) { + if (ap.type != kFileTypeDirectory && ap.rawFd < 0) { if (nextEntryIdx == 0) { // The first item is typically the framework resources, // which we want to avoid parsing every time. @@ -609,7 +651,8 @@ const ResTable* AssetManager::getResTable(bool required) const bool onlyEmptyResources = true; const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { - bool empty = appendPathToResTable(mAssetPaths.itemAt(i)); + bool empty = appendPathToResTable( + const_cast<AssetManager*>(this)->mAssetPaths.editItemAt(i)); onlyEmptyResources = onlyEmptyResources && empty; } @@ -734,10 +777,12 @@ void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocale * be used. */ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode, - const asset_path& ap) + asset_path& ap) { Asset* pAsset = NULL; + ALOGV("openNonAssetInPath: name=%s type=%d fd=%d", fileName, ap.type, ap.rawFd); + /* look at the filesystem on disk */ if (ap.type == kFileTypeDirectory) { String8 path(ap.path); @@ -752,7 +797,7 @@ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode m } if (pAsset != NULL) { - //printf("FOUND NA '%s' on disk\n", fileName); + ALOGV("FOUND NA '%s' on disk", fileName); pAsset->setAssetSource(path); } @@ -763,10 +808,10 @@ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode m /* check the appropriate Zip file */ ZipFileRO* pZip = getZipFileLocked(ap); if (pZip != NULL) { - //printf("GOT zip, checking NA '%s'\n", (const char*) path); + ALOGV("GOT zip, checking NA '%s'", (const char*) path); ZipEntryRO entry = pZip->findEntryByName(path.string()); if (entry != NULL) { - //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); + ALOGV("FOUND NA in Zip file for %s", (const char*) path); pAsset = openAssetFromZipLocked(pZip, entry, mode, path); pZip->releaseEntry(entry); } @@ -813,11 +858,23 @@ String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* roo * Return a pointer to one of our open Zip archives. Returns NULL if no * matching Zip file exists. */ -ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap) +ZipFileRO* AssetManager::getZipFileLocked(asset_path& ap) { - ALOGV("getZipFileLocked() in %p\n", this); + ALOGV("getZipFileLocked() in %p: ap=%p zip=%p", this, &ap, ap.zip.get()); + + if (ap.zip != NULL) { + return ap.zip->getZip(); + } + + if (ap.rawFd < 0) { + ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.string()); + ap.zip = mZipSet.getSharedZip(ap.path); + } else { + ALOGV("getZipFileLocked: Creating new zip from fd %d", ap.rawFd); + ap.zip = SharedZip::create(ap.rawFd, ap.path); - return mZipSet.getZip(ap.path); + } + return ap.zip != NULL ? ap.zip->getZip() : NULL; } /* @@ -1374,6 +1431,21 @@ AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) } } +AssetManager::SharedZip::SharedZip(int fd, const String8& path) + : mPath(path), mZipFile(NULL), mModWhen(0), + mResourceTableAsset(NULL), mResourceTable(NULL) +{ + if (kIsDebug) { + ALOGI("Creating SharedZip %p fd=%d %s\n", this, fd, (const char*)mPath); + } + ALOGV("+++ opening zip fd=%d '%s'\n", fd, mPath.string()); + mZipFile = ZipFileRO::openFd(fd, mPath.string()); + if (mZipFile == NULL) { + ::close(fd); + ALOGD("failed to open Zip archive fd=%d '%s'\n", fd, mPath.string()); + } +} + sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path, bool createIfNotPresent) { @@ -1389,7 +1461,11 @@ sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path, zip = new SharedZip(path, modWhen); gOpen.add(path, zip); return zip; +} +sp<AssetManager::SharedZip> AssetManager::SharedZip::create(int fd, const String8& path) +{ + return new SharedZip(fd, path); } ZipFileRO* AssetManager::SharedZip::getZip() @@ -1500,19 +1576,23 @@ void AssetManager::ZipSet::closeZip(int idx) mZipFile.editItemAt(idx) = NULL; } - /* * Retrieve the appropriate Zip file from the set. */ ZipFileRO* AssetManager::ZipSet::getZip(const String8& path) { + return getSharedZip(path)->getZip(); +} + +const sp<AssetManager::SharedZip> AssetManager::ZipSet::getSharedZip(const String8& path) +{ int idx = getIndex(path); sp<SharedZip> zip = mZipFile[idx]; if (zip == NULL) { zip = SharedZip::get(path); mZipFile.editItemAt(idx) = zip; } - return zip->getZip(); + return zip; } Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path) diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 5667f9283241..9c1629bc36f5 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -18,6 +18,8 @@ #include "androidfw/AssetManager2.h" +#include <algorithm> +#include <iterator> #include <set> #include "android-base/logging.h" @@ -35,12 +37,40 @@ namespace android { -AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); } +struct FindEntryResult { + // A pointer to the resource table entry for this resource. + // If the size of the entry is > sizeof(ResTable_entry), it can be cast to + // a ResTable_map_entry and processed as a bag/map. + const ResTable_entry* entry; + + // The configuration for which the resulting entry was defined. This is already swapped to host + // endianness. + ResTable_config config; + + // The bitmask of configuration axis with which the resource value varies. + uint32_t type_flags; + + // The dynamic package ID map for the package from which this resource came from. + const DynamicRefTable* dynamic_ref_table; + + // The string pool reference to the type's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef type_string_ref; + + // The string pool reference to the entry's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef entry_string_ref; +}; + +AssetManager2::AssetManager2() { + memset(&configuration_, 0, sizeof(configuration_)); +} bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches) { apk_assets_ = apk_assets; BuildDynamicRefTable(); + RebuildFilterList(); if (invalidate_caches) { InvalidateCaches(static_cast<uint32_t>(-1)); } @@ -55,9 +85,9 @@ void AssetManager2::BuildDynamicRefTable() { int next_package_id = 0x02; const size_t apk_assets_count = apk_assets_.size(); for (size_t i = 0; i < apk_assets_count; i++) { - const ApkAssets* apk_asset = apk_assets_[i]; - for (const std::unique_ptr<const LoadedPackage>& package : - apk_asset->GetLoadedArsc()->GetPackages()) { + const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc(); + + for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) { // Get the package ID or assign one if a shared library. int package_id; if (package->IsDynamic()) { @@ -71,12 +101,14 @@ void AssetManager2::BuildDynamicRefTable() { if (idx == 0xff) { package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size()); package_groups_.push_back({}); - package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id; + DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table; + ref_table.mAssignedPackageId = package_id; + ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f; } PackageGroup* package_group = &package_groups_[idx]; // Add the package and to the set of packages with the same ID. - package_group->packages_.push_back(package.get()); + package_group->packages_.push_back(ConfiguredPackage{package.get(), {}}); package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i)); // Add the package name -> build time ID mappings. @@ -91,7 +123,7 @@ void AssetManager2::BuildDynamicRefTable() { // Now assign the runtime IDs so that we have a build-time to runtime ID map. const auto package_groups_end = package_groups_.end(); for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) { - const std::string& package_name = iter->packages_[0]->GetPackageName(); + const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName(); for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) { iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()), iter->dynamic_ref_table.mAssignedPackageId); @@ -102,20 +134,33 @@ void AssetManager2::BuildDynamicRefTable() { void AssetManager2::DumpToLog() const { base::ScopedLogSeverity _log(base::INFO); + LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this); + std::string list; + for (const auto& apk_assets : apk_assets_) { + base::StringAppendF(&list, "%s,", apk_assets->GetPath().c_str()); + } + LOG(INFO) << "ApkAssets: " << list; + + list = ""; for (size_t i = 0; i < package_ids_.size(); i++) { if (package_ids_[i] != 0xff) { - base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]); + base::StringAppendF(&list, "%02x -> %d, ", (int)i, package_ids_[i]); } } LOG(INFO) << "Package ID map: " << list; for (const auto& package_group: package_groups_) { - list = ""; - for (const auto& package : package_group.packages_) { - base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId()); - } - LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list; + list = ""; + for (const auto& package : package_group.packages_) { + const LoadedPackage* loaded_package = package.loaded_package_; + base::StringAppendF(&list, "%s(%02x%s), ", loaded_package->GetPackageName().c_str(), + loaded_package->GetPackageId(), + (loaded_package->IsDynamic() ? " dynamic" : "")); + } + LOG(INFO) << base::StringPrintf("PG (%02x): ", + package_group.dynamic_ref_table.mAssignedPackageId) + << list; } } @@ -154,53 +199,55 @@ void AssetManager2::SetConfiguration(const ResTable_config& configuration) { configuration_ = configuration; if (diff) { + RebuildFilterList(); InvalidateCaches(static_cast<uint32_t>(diff)); } } std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system, - bool exclude_mipmap) { - ATRACE_CALL(); + bool exclude_mipmap) const { + ATRACE_NAME("AssetManager::GetResourceConfigurations"); std::set<ResTable_config> configurations; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectConfigurations(exclude_mipmap, &configurations); + package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations); } } return configurations; } std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system, - bool merge_equivalent_languages) { - ATRACE_CALL(); + bool merge_equivalent_languages) const { + ATRACE_NAME("AssetManager::GetResourceLocales"); std::set<std::string> locales; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectLocales(merge_equivalent_languages, &locales); + package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales); } } return locales; } -std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) { +std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, mode); } std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode) { + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, cookie, mode); } -std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { - ATRACE_CALL(); +std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const { + ATRACE_NAME("AssetManager::OpenDir"); std::string full_path = "assets/" + dirname; std::unique_ptr<SortedVector<AssetDir::FileInfo>> files = @@ -233,8 +280,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { // is inconsistent for split APKs. std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie) { - ATRACE_CALL(); + ApkAssetsCookie* out_cookie) const { for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) { std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode); if (asset) { @@ -252,8 +298,8 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, - ApkAssetsCookie cookie, Asset::AccessMode mode) { - ATRACE_CALL(); + ApkAssetsCookie cookie, + Asset::AccessMode mode) const { if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) { return {}; } @@ -261,16 +307,13 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override, - bool stop_at_first_match, LoadedArscEntry* out_entry, - ResTable_config* out_selected_config, - uint32_t* out_flags) { - ATRACE_CALL(); - + bool /*stop_at_first_match*/, + FindEntryResult* out_entry) const { // Might use this if density_override != 0. ResTable_config density_override_config; // Select our configuration or generate a density override configuration. - ResTable_config* desired_config = &configuration_; + const ResTable_config* desired_config = &configuration_; if (density_override != 0 && density_override != configuration_.density) { density_override_config = configuration_; density_override_config.density = density_override; @@ -284,68 +327,144 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri const uint32_t package_id = get_package_id(resid); const uint8_t type_idx = get_type_id(resid) - 1; - const uint16_t entry_id = get_entry_id(resid); + const uint16_t entry_idx = get_entry_id(resid); - const uint8_t idx = package_ids_[package_id]; - if (idx == 0xff) { + const uint8_t package_idx = package_ids_[package_id]; + if (package_idx == 0xff) { LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid); return kInvalidCookie; } - LoadedArscEntry best_entry; - ResTable_config best_config; - ApkAssetsCookie best_cookie = kInvalidCookie; - uint32_t cumulated_flags = 0u; - - const PackageGroup& package_group = package_groups_[idx]; + const PackageGroup& package_group = package_groups_[package_idx]; const size_t package_count = package_group.packages_.size(); - for (size_t i = 0; i < package_count; i++) { - LoadedArscEntry current_entry; - ResTable_config current_config; - uint32_t current_flags = 0; - - const LoadedPackage* loaded_package = package_group.packages_[i]; - if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry, - ¤t_config, ¤t_flags)) { + + ApkAssetsCookie best_cookie = kInvalidCookie; + const LoadedPackage* best_package = nullptr; + const ResTable_type* best_type = nullptr; + const ResTable_config* best_config = nullptr; + ResTable_config best_config_copy; + uint32_t best_offset = 0u; + uint32_t type_flags = 0u; + + // If desired_config is the same as the set configuration, then we can use our filtered list + // and we don't need to match the configurations, since they already matched. + const bool use_fast_path = desired_config == &configuration_; + + for (size_t pi = 0; pi < package_count; pi++) { + const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; + const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_; + ApkAssetsCookie cookie = package_group.cookies_[pi]; + + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx); + if (UNLIKELY(type_spec == nullptr)) { continue; } - cumulated_flags |= current_flags; + uint16_t local_entry_idx = entry_idx; - if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config)) { - best_entry = current_entry; - best_config = current_config; - best_cookie = package_group.cookies_[i]; - if (stop_at_first_match) { - break; + // If there is an IDMAP supplied with this package, translate the entry ID. + if (type_spec->idmap_entries != nullptr) { + if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) { + // There is no mapping, so the resource is not meant to be in this overlay package. + continue; + } + } + + type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx); + + // If the package is an overlay, then even configurations that are the same MUST be chosen. + const bool package_is_overlay = loaded_package->IsOverlay(); + + const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; + if (use_fast_path) { + const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations; + const size_t type_count = candidate_configs.size(); + for (uint32_t i = 0; i < type_count; i++) { + const ResTable_config& this_config = candidate_configs[i]; + + // We can skip calling ResTable_config::match() because we know that all candidate + // configurations that do NOT match have been filtered-out. + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const ResTable_type* type_chunk = filtered_group.types[i]; + const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = type_chunk; + best_config = &this_config; + best_offset = offset; + } + } + } else { + // This is the slower path, which doesn't use the filtered list of configurations. + // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness + // and fill in any new fields that did not exist when the APK was compiled. + // Furthermore when selecting configurations we can't just record the pointer to the + // ResTable_config, we must copy it. + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + + if (this_config.match(*desired_config)) { + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = *iter; + best_config_copy = this_config; + best_config = &best_config_copy; + best_offset = offset; + } + } } } } - if (best_cookie == kInvalidCookie) { + if (UNLIKELY(best_cookie == kInvalidCookie)) { return kInvalidCookie; } - *out_entry = best_entry; + const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset); + if (UNLIKELY(best_entry == nullptr)) { + return kInvalidCookie; + } + + out_entry->entry = best_entry; + out_entry->config = *best_config; + out_entry->type_flags = type_flags; + out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1); + out_entry->entry_string_ref = + StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index); out_entry->dynamic_ref_table = &package_group.dynamic_ref_table; - *out_selected_config = best_config; - *out_flags = cumulated_flags; return best_cookie; } -bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { - ATRACE_CALL(); - - LoadedArscEntry entry; - ResTable_config config; - uint32_t flags = 0u; - ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, - true /* stop_at_first_match */, &entry, &config, &flags); +bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const { + FindEntryResult entry; + ApkAssetsCookie cookie = + FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, &entry); if (cookie == kInvalidCookie) { return false; } - const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid); + const LoadedPackage* package = + apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); if (package == nullptr) { return false; } @@ -373,30 +492,29 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return true; } -bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { - LoadedArscEntry entry; - ResTable_config config; - ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, - false /* stop_at_first_match */, &entry, &config, out_flags); - return cookie != kInvalidCookie; +bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const { + FindEntryResult entry; + ApkAssetsCookie cookie = + FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry); + if (cookie != kInvalidCookie) { + *out_flags = entry.type_flags; + return cookie; + } + return kInvalidCookie; } ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags) { - ATRACE_CALL(); - - LoadedArscEntry entry; - ResTable_config config; - uint32_t flags = 0u; + uint32_t* out_flags) const { + FindEntryResult entry; ApkAssetsCookie cookie = - FindEntry(resid, density_override, false /* stop_at_first_match */, &entry, &config, &flags); + FindEntry(resid, density_override, false /* stop_at_first_match */, &entry); if (cookie == kInvalidCookie) { return kInvalidCookie; } - if (dtohl(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) { + if (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) { if (!may_be_bag) { LOG(ERROR) << base::StringPrintf("Resource %08x is a complex map type.", resid); return kInvalidCookie; @@ -405,8 +523,8 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Create a reference since we can't represent this complex type as a Res_value. out_value->dataType = Res_value::TYPE_REFERENCE; out_value->data = resid; - *out_selected_config = config; - *out_flags = flags; + *out_selected_config = entry.config; + *out_flags = entry.type_flags; return cookie; } @@ -417,25 +535,21 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Convert the package ID to the runtime assigned package ID. entry.dynamic_ref_table->lookupResourceValue(out_value); - *out_selected_config = config; - *out_flags = flags; + *out_selected_config = entry.config; + *out_flags = entry.type_flags; return cookie; } ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference) { - ATRACE_CALL(); + uint32_t* out_last_reference) const { constexpr const int kMaxIterations = 20; - *out_last_reference = 0u; for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE && in_out_value->data != 0u && iteration < kMaxIterations; iteration++) { - if (out_last_reference != nullptr) { - *out_last_reference = in_out_value->data; - } + *out_last_reference = in_out_value->data; uint32_t new_flags = 0u; cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/, in_out_value, in_out_selected_config, &new_flags); @@ -454,18 +568,21 @@ ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_valu } const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { - ATRACE_CALL(); + auto found_resids = std::vector<uint32_t>(); + return GetBag(resid, found_resids); +} + +const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) { + ATRACE_NAME("AssetManager::GetBag"); auto cached_iter = cached_bags_.find(resid); if (cached_iter != cached_bags_.end()) { return cached_iter->second.get(); } - LoadedArscEntry entry; - ResTable_config config; - uint32_t flags = 0u; - ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, - false /* stop_at_first_match */, &entry, &config, &flags); + FindEntryResult entry; + ApkAssetsCookie cookie = + FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry); if (cookie == kInvalidCookie) { return nullptr; } @@ -484,10 +601,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { reinterpret_cast<const ResTable_map*>(reinterpret_cast<const uint8_t*>(map) + map->size); const ResTable_map* const map_entry_end = map_entry + dtohl(map->count); + // Keep track of ids that have already been seen to prevent infinite loops caused by circular + // dependencies between bags + child_resids.push_back(resid); + uint32_t parent_resid = dtohl(map->parent.ident); - if (parent_resid == 0) { - // There is no parent, meaning there is nothing to inherit and we can do a simple - // copy of the entries in the map. + if (parent_resid == 0 || std::find(child_resids.begin(), child_resids.end(), parent_resid) + != child_resids.end()) { + // There is no parent or that a circular dependency exist, meaning there is nothing to + // inherit and we can do a simple copy of the entries in the map. const size_t entry_count = map_entry_end - map_entry; util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>( malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))}; @@ -498,18 +620,26 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // Attributes, arrays, etc don't have a resource id as the name. They specify // other data, which would be wrong to change via a lookup. if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } new_entry->cookie = cookie; - new_entry->value.copyFrom_dtoh(map_entry->value); new_entry->key = new_key; new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; + new_entry->value.copyFrom_dtoh(map_entry->value); + status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value); + if (err != NO_ERROR) { + LOG(ERROR) << base::StringPrintf( + "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType, + new_entry->value.data, new_key); + return nullptr; + } ++new_entry; } - new_bag->type_spec_flags = flags; + new_bag->type_spec_flags = entry.type_flags; new_bag->entry_count = static_cast<uint32_t>(entry_count); ResolvedBag* result = new_bag.get(); cached_bags_[resid] = std::move(new_bag); @@ -520,21 +650,19 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { entry.dynamic_ref_table->lookupResourceId(&parent_resid); // Get the parent and do a merge of the keys. - const ResolvedBag* parent_bag = GetBag(parent_resid); + const ResolvedBag* parent_bag = GetBag(parent_resid, child_resids); if (parent_bag == nullptr) { // Failed to get the parent that should exist. - LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid); + LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, + resid); return nullptr; } - // Combine flags from the parent and our own bag. - flags |= parent_bag->type_spec_flags; - // Create the max possible entries we can make. Once we construct the bag, // we will realloc to fit to size. const size_t max_count = parent_bag->entry_count + dtohl(map->count); - ResolvedBag* new_bag = reinterpret_cast<ResolvedBag*>( - malloc(sizeof(ResolvedBag) + (max_count * sizeof(ResolvedBag::Entry)))); + util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>( + malloc(sizeof(ResolvedBag) + (max_count * sizeof(ResolvedBag::Entry))))}; ResolvedBag::Entry* new_entry = new_bag->entries; const ResolvedBag::Entry* parent_entry = parent_bag->entries; @@ -545,7 +673,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t child_key = dtohl(map_entry->name.ident); if (!is_internal_resid(child_key)) { if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, + resid); return nullptr; } } @@ -554,10 +683,17 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // Use the child key if it comes before the parent // or is equal to the parent (overrides). new_entry->cookie = cookie; - new_entry->value.copyFrom_dtoh(map_entry->value); new_entry->key = child_key; new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; + new_entry->value.copyFrom_dtoh(map_entry->value); + status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value); + if (err != NO_ERROR) { + LOG(ERROR) << base::StringPrintf( + "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType, + new_entry->value.data, child_key); + return nullptr; + } ++map_entry; } else { // Take the parent entry as-is. @@ -577,15 +713,22 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t new_key = dtohl(map_entry->name.ident); if (!is_internal_resid(new_key)) { if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } new_entry->cookie = cookie; - new_entry->value.copyFrom_dtoh(map_entry->value); new_entry->key = new_key; new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; + new_entry->value.copyFrom_dtoh(map_entry->value); + status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value); + if (err != NO_ERROR) { + LOG(ERROR) << base::StringPrintf("Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", + new_entry->value.dataType, new_entry->value.data, new_key); + return nullptr; + } ++map_entry; ++new_entry; } @@ -601,15 +744,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // Resize the resulting array to fit. const size_t actual_count = new_entry - new_bag->entries; if (actual_count != max_count) { - new_bag = reinterpret_cast<ResolvedBag*>( - realloc(new_bag, sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))); + new_bag.reset(reinterpret_cast<ResolvedBag*>(realloc( + new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry))))); } - util::unique_cptr<ResolvedBag> final_bag{new_bag}; - final_bag->type_spec_flags = flags; - final_bag->entry_count = static_cast<uint32_t>(actual_count); - ResolvedBag* result = final_bag.get(); - cached_bags_[resid] = std::move(final_bag); + // Combine flags from the parent and our own bag. + new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags; + new_bag->entry_count = static_cast<uint32_t>(actual_count); + ResolvedBag* result = new_bag.get(); + cached_bags_[resid] = std::move(new_bag); return result; } @@ -627,7 +770,7 @@ static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) { uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const std::string& fallback_type, - const std::string& fallback_package) { + const std::string& fallback_package) const { StringPiece package_name, type, entry; if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) { return 0u; @@ -659,7 +802,8 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const static std::u16string kAttrPrivate16 = u"^attr-private"; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { + for (const ConfiguredPackage& package_impl : package_group.packages_) { + const LoadedPackage* package = package_impl.loaded_package_; if (package_name != package->GetPackageName()) { // All packages in the same group are expected to have the same package name. break; @@ -681,6 +825,32 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, return 0u; } +void AssetManager2::RebuildFilterList() { + for (PackageGroup& group : package_groups_) { + for (ConfiguredPackage& impl : group.packages_) { + // Destroy it. + impl.filtered_configs_.~ByteBucketArray(); + + // Re-create it. + new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); + + // Create the filters here. + impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) { + FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index); + const auto iter_end = spec->types + spec->type_count; + for (auto iter = spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + if (this_config.match(configuration_)) { + group.configurations.push_back(this_config); + group.types.push_back(*iter); + } + } + }); + } + } +} + void AssetManager2::InvalidateCaches(uint32_t diff) { if (diff == 0xffffffffu) { // Everything must go. @@ -699,10 +869,40 @@ void AssetManager2::InvalidateCaches(uint32_t diff) { } } -std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); } +std::unique_ptr<Theme> AssetManager2::NewTheme() { + return std::unique_ptr<Theme>(new Theme(this)); +} + +Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) { +} + +Theme::~Theme() = default; + +namespace { + +struct ThemeEntry { + ApkAssetsCookie cookie; + uint32_t type_spec_flags; + Res_value value; +}; + +struct ThemeType { + int entry_count; + ThemeEntry entries[0]; +}; + +constexpr size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1; + +} // namespace + +struct Theme::Package { + // Each element of Type will be a dynamically sized object + // allocated to have the entries stored contiguously with the Type. + std::array<util::unique_cptr<ThemeType>, kTypeCount> types; +}; bool Theme::ApplyStyle(uint32_t resid, bool force) { - ATRACE_CALL(); + ATRACE_NAME("Theme::ApplyStyle"); const ResolvedBag* bag = asset_manager_->GetBag(resid); if (bag == nullptr) { @@ -712,71 +912,69 @@ bool Theme::ApplyStyle(uint32_t resid, bool force) { // Merge the flags from this style. type_spec_flags_ |= bag->type_spec_flags; - // On the first iteration, verify the attribute IDs and - // update the entry count in each type. - const auto bag_iter_end = end(bag); - for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) { + int last_type_idx = -1; + int last_package_idx = -1; + Package* last_package = nullptr; + ThemeType* last_type = nullptr; + + // Iterate backwards, because each bag is sorted in ascending key ID order, meaning we will only + // need to perform one resize per type. + using reverse_bag_iterator = std::reverse_iterator<const ResolvedBag::Entry*>; + const auto bag_iter_end = reverse_bag_iterator(begin(bag)); + for (auto bag_iter = reverse_bag_iterator(end(bag)); bag_iter != bag_iter_end; ++bag_iter) { const uint32_t attr_resid = bag_iter->key; - // If the resource ID passed in is not a style, the key can be - // some other identifier that is not a resource ID. + // If the resource ID passed in is not a style, the key can be some other identifier that is not + // a resource ID. We should fail fast instead of operating with strange resource IDs. if (!is_valid_resid(attr_resid)) { return false; } - const uint32_t package_idx = get_package_id(attr_resid); - - // The type ID is 1-based, so subtract 1 to get an index. - const uint32_t type_idx = get_type_id(attr_resid) - 1; - const uint32_t entry_idx = get_entry_id(attr_resid); - - std::unique_ptr<Package>& package = packages_[package_idx]; - if (package == nullptr) { - package.reset(new Package()); - } - - util::unique_cptr<Type>& type = package->types[type_idx]; - if (type == nullptr) { - // Set the initial capacity to take up a total amount of 1024 bytes. - constexpr uint32_t kInitialCapacity = (1024u - sizeof(Type)) / sizeof(Entry); - const uint32_t initial_capacity = std::max(entry_idx, kInitialCapacity); - type.reset( - reinterpret_cast<Type*>(calloc(sizeof(Type) + (initial_capacity * sizeof(Entry)), 1))); - type->entry_capacity = initial_capacity; + // We don't use the 0-based index for the type so that we can avoid doing ID validation + // upon lookup. Instead, we keep space for the type ID 0 in our data structures. Since + // the construction of this type is guarded with a resource ID check, it will never be + // populated, and querying type ID 0 will always fail. + const int package_idx = get_package_id(attr_resid); + const int type_idx = get_type_id(attr_resid); + const int entry_idx = get_entry_id(attr_resid); + + if (last_package_idx != package_idx) { + std::unique_ptr<Package>& package = packages_[package_idx]; + if (package == nullptr) { + package.reset(new Package()); + } + last_package_idx = package_idx; + last_package = package.get(); + last_type_idx = -1; } - // Set the entry_count to include this entry. We will populate - // and resize the array as necessary in the next pass. - if (entry_idx + 1 > type->entry_count) { - // Increase the entry count to include this. - type->entry_count = entry_idx + 1; - } - } - - // On the second pass, we will realloc to fit the entry counts - // and populate the structures. - for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) { - const uint32_t attr_resid = bag_iter->key; - const uint32_t package_idx = get_package_id(attr_resid); - const uint32_t type_idx = get_type_id(attr_resid) - 1; - const uint32_t entry_idx = get_entry_id(attr_resid); - Package* package = packages_[package_idx].get(); - util::unique_cptr<Type>& type = package->types[type_idx]; - if (type->entry_count != type->entry_capacity) { - // Resize to fit the actual entries that will be included. - Type* type_ptr = type.release(); - type.reset(reinterpret_cast<Type*>( - realloc(type_ptr, sizeof(Type) + (type_ptr->entry_count * sizeof(Entry))))); - if (type->entry_capacity < type->entry_count) { - // Clear the newly allocated memory (which does not get zero initialized). - // We need to do this because we |= type_spec_flags. - memset(type->entries + type->entry_capacity, 0, - sizeof(Entry) * (type->entry_count - type->entry_capacity)); + if (last_type_idx != type_idx) { + util::unique_cptr<ThemeType>& type = last_package->types[type_idx]; + if (type == nullptr) { + // Allocate enough memory to contain this entry_idx. Since we're iterating in reverse over + // a sorted list of attributes, this shouldn't be resized again during this method call. + type.reset(reinterpret_cast<ThemeType*>( + calloc(sizeof(ThemeType) + (entry_idx + 1) * sizeof(ThemeEntry), 1))); + type->entry_count = entry_idx + 1; + } else if (entry_idx >= type->entry_count) { + // Reallocate the memory to contain this entry_idx. Since we're iterating in reverse over + // a sorted list of attributes, this shouldn't be resized again during this method call. + const int new_count = entry_idx + 1; + type.reset(reinterpret_cast<ThemeType*>( + realloc(type.release(), sizeof(ThemeType) + (new_count * sizeof(ThemeEntry))))); + + // Clear out the newly allocated space (which isn't zeroed). + memset(type->entries + type->entry_count, 0, + (new_count - type->entry_count) * sizeof(ThemeEntry)); + type->entry_count = new_count; } - type->entry_capacity = type->entry_count; + last_type_idx = type_idx; + last_type = type.get(); } - Entry& entry = type->entries[entry_idx]; - if (force || entry.value.dataType == Res_value::TYPE_NULL) { + + ThemeEntry& entry = last_type->entries[entry_idx]; + if (force || (entry.value.dataType == Res_value::TYPE_NULL && + entry.value.data != Res_value::DATA_NULL_EMPTY)) { entry.cookie = bag_iter->cookie; entry.type_spec_flags |= bag->type_spec_flags; entry.value = bag_iter->value; @@ -787,95 +985,53 @@ bool Theme::ApplyStyle(uint32_t resid, bool force) { ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value, uint32_t* out_flags) const { - constexpr const int kMaxIterations = 20; + int cnt = 20; uint32_t type_spec_flags = 0u; - for (int iterations_left = kMaxIterations; iterations_left > 0; iterations_left--) { - if (!is_valid_resid(resid)) { - return kInvalidCookie; - } - - const uint32_t package_idx = get_package_id(resid); - - // Type ID is 1-based, subtract 1 to get the index. - const uint32_t type_idx = get_type_id(resid) - 1; - const uint32_t entry_idx = get_entry_id(resid); - + do { + const int package_idx = get_package_id(resid); const Package* package = packages_[package_idx].get(); - if (package == nullptr) { - return kInvalidCookie; - } - - const Type* type = package->types[type_idx].get(); - if (type == nullptr) { - return kInvalidCookie; - } - - if (entry_idx >= type->entry_count) { - return kInvalidCookie; - } - - const Entry& entry = type->entries[entry_idx]; - type_spec_flags |= entry.type_spec_flags; - - switch (entry.value.dataType) { - case Res_value::TYPE_NULL: - return kInvalidCookie; - - case Res_value::TYPE_ATTRIBUTE: - resid = entry.value.data; - break; - - case Res_value::TYPE_DYNAMIC_ATTRIBUTE: { - // Resolve the dynamic attribute to a normal attribute - // (with the right package ID). - resid = entry.value.data; - const DynamicRefTable* ref_table = - asset_manager_->GetDynamicRefTableForPackage(package_idx); - if (ref_table == nullptr || ref_table->lookupResourceId(&resid) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic attribute 0x%08x", resid); - return kInvalidCookie; - } - } break; - - case Res_value::TYPE_DYNAMIC_REFERENCE: { - // Resolve the dynamic reference to a normal reference - // (with the right package ID). - out_value->dataType = Res_value::TYPE_REFERENCE; - out_value->data = entry.value.data; - const DynamicRefTable* ref_table = - asset_manager_->GetDynamicRefTableForPackage(package_idx); - if (ref_table == nullptr || ref_table->lookupResourceId(&out_value->data) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic reference 0x%08x", - out_value->data); - return kInvalidCookie; - } - - if (out_flags != nullptr) { + if (package != nullptr) { + // The themes are constructed with a 1-based type ID, so no need to decrement here. + const int type_idx = get_type_id(resid); + const ThemeType* type = package->types[type_idx].get(); + if (type != nullptr) { + const int entry_idx = get_entry_id(resid); + if (entry_idx < type->entry_count) { + const ThemeEntry& entry = type->entries[entry_idx]; + type_spec_flags |= entry.type_spec_flags; + + if (entry.value.dataType == Res_value::TYPE_ATTRIBUTE) { + if (cnt > 0) { + cnt--; + resid = entry.value.data; + continue; + } + return kInvalidCookie; + } + + // @null is different than @empty. + if (entry.value.dataType == Res_value::TYPE_NULL && + entry.value.data != Res_value::DATA_NULL_EMPTY) { + return kInvalidCookie; + } + + *out_value = entry.value; *out_flags = type_spec_flags; + return entry.cookie; } - return entry.cookie; } - - default: - *out_value = entry.value; - if (out_flags != nullptr) { - *out_flags = type_spec_flags; - } - return entry.cookie; } - } - - LOG(WARNING) << base::StringPrintf("Too many (%d) attribute references, stopped at: 0x%08x", - kMaxIterations, resid); + break; + } while (true); return kInvalidCookie; } ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_type_spec_flags, - uint32_t* out_last_ref) { + uint32_t* out_last_ref) const { if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) { uint32_t new_flags; cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags); @@ -903,30 +1059,36 @@ bool Theme::SetTo(const Theme& o) { return true; } - if (asset_manager_ != o.asset_manager_) { - return false; - } - 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) { + 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 Type* type = package->types[t].get(); + 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; } - const size_t type_alloc_size = sizeof(Type) + (type->entry_capacity * sizeof(Entry)); + // 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<Type*>(copied_data)); + packages_[p]->types[t].reset(reinterpret_cast<ThemeType*>(copied_data)); } } return true; diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index 60e3845d98a9..f912af4f7190 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -20,13 +20,18 @@ #include <log/log.h> +#include "androidfw/AssetManager2.h" #include "androidfw/AttributeFinder.h" -#include "androidfw/ResourceTypes.h" constexpr bool kDebugStyles = false; namespace android { +// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. +static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { + return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1); +} + class XmlAttributeFinder : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> { public: @@ -44,58 +49,53 @@ class XmlAttributeFinder }; class BagAttributeFinder - : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> { + : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> { public: - BagAttributeFinder(const ResTable::bag_entry* start, - const ResTable::bag_entry* end) - : BackTrackingAttributeFinder(start, end) {} + BagAttributeFinder(const ResolvedBag* bag) + : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr, + bag != nullptr ? bag->entries + bag->entry_count : nullptr) { + } - inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const { - return entry->map.name.ident; + inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const { + return entry->key; } }; -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, - uint32_t def_style_res, uint32_t* src_values, - size_t src_values_length, uint32_t* attrs, - size_t attrs_length, uint32_t* out_values, - uint32_t* out_indices) { +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, + uint32_t* src_values, size_t src_values_length, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme, def_style_attr, def_style_res); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { def_style_res = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_end = - def_style_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_res != 0) { + default_style_bag = assetmanager->GetBag(def_style_res); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -106,7 +106,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = -1; + ApkAssetsCookie cookie = kInvalidCookie; uint32_t type_set_flags = 0; value.dataType = Res_value::TYPE_NULL; @@ -122,15 +122,14 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, value.dataType = Res_value::TYPE_ATTRIBUTE; value.data = src_values[ii]; if (kDebugStyles) { - ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, - value.data); + ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data); } } else { - const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_entry != def_style_end) { - block = def_style_entry->stringBlock; - type_set_flags = def_style_type_set_flags; - value = def_style_entry->map.value; + const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -140,22 +139,26 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t resid = 0; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data); } } else if (value.data != Res_value::DATA_NULL_EMPTY) { - // If we still don't have a value for this attribute, try to find - // it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + // If we still don't have a value for this attribute, try to find it in the theme! + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -169,7 +172,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = -1; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -179,9 +182,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != -1 ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -195,90 +196,80 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } return true; } -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { - ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", - theme, def_style_attr, def_style_res, xml_parser); + ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme, + def_style_attr, def_style_resid, xml_parser); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, - &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { - def_style_res = value.data; + def_style_resid = value.data; } } } - // Retrieve the style class associated with the current XML tag. - int style = 0; - uint32_t style_bag_type_set_flags = 0; + // Retrieve the style resource ID associated with the current XML tag's style attribute. + uint32_t style_resid = 0u; + uint32_t style_flags = 0u; if (xml_parser != nullptr) { ssize_t idx = xml_parser->indexOfStyle(); if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) { if (value.dataType == value.TYPE_ATTRIBUTE) { - if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) { + // Resolve the attribute with out theme. + if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) { value.dataType = Res_value::TYPE_NULL; } } + if (value.dataType == value.TYPE_REFERENCE) { - style = value.data; + style_resid = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_attr_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_attr_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_attr_end = - def_style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_attr_start, - def_style_attr_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_resid != 0) { + default_style_bag = assetmanager->GetBag(def_style_resid); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Retrieve the style class bag, if requested. - const ResTable::bag_entry* style_attr_start = nullptr; - uint32_t style_type_set_flags = 0; - bag_off = - style != 0 - ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags) - : -1; - style_type_set_flags |= style_bag_type_set_flags; - const ResTable::bag_entry* const style_attr_end = - style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end); + const ResolvedBag* xml_style_bag = nullptr; + if (style_resid != 0) { + xml_style_bag = assetmanager->GetBag(style_resid); + if (xml_style_bag != nullptr) { + style_flags |= xml_style_bag->type_spec_flags; + } + } + + BagAttributeFinder xml_style_attr_finder(xml_style_bag); // Retrieve the XML attributes, if requested. - static const ssize_t kXmlBlock = 0x10000000; XmlAttributeFinder xml_attr_finder(xml_parser); - const size_t xml_attr_end = - xml_parser != nullptr ? xml_parser->getAttributeCount() : 0; // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -289,8 +280,8 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -302,7 +293,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Walk through the xml attributes looking for the requested attribute. const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident); - if (xml_attr_idx != xml_attr_end) { + if (xml_attr_idx != xml_attr_finder.end()) { // We found the attribute we were looking for. xml_parser->getAttributeValue(xml_attr_idx, &value); if (kDebugStyles) { @@ -312,12 +303,12 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the style class values looking for the requested attribute. - const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident); - if (style_attr_entry != style_attr_end) { + const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident); + if (entry != xml_style_attr_finder.end()) { // We found the attribute we were looking for. - block = style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -326,25 +317,25 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the default style values looking for the requested attribute. - const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_attr_entry != def_style_attr_end) { + const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { // We found the attribute we were looking for. - block = def_style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = def_style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } } } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -352,14 +343,15 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } } else if (value.data != Res_value::DATA_NULL_EMPTY) { // If we still don't have a value for this attribute, try to find it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -375,7 +367,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -385,9 +377,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -402,36 +392,28 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - // out_indices must NOT be nullptr. out_indices[0] = indices_idx; } -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, - uint32_t* attrs, size_t attrs_length, - uint32_t* out_values, uint32_t* out_indices) { +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { ResTable_config config; Res_value value; int indices_idx = 0; - // Now lock down the resource object and start pulling stuff from it. - res->lock(); - // Retrieve the XML attributes, if requested. const size_t xml_attr_count = xml_parser->getAttributeCount(); size_t ix = 0; uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix); - static const ssize_t kXmlBlock = 0x10000000; - // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. for (size_t ii = 0; ii < attrs_length; ii++) { const uint32_t cur_ident = attrs[ii]; - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -450,28 +432,27 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, cur_xml_attr = xml_parser->getAttributeNameResID(ix); } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - // printf("Resolving attribute reference\n"); - ssize_t new_block = res->resolveReference(&value, block, &resid, - &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } } // Deal with the special @null value -- it turns back to TYPE_NULL. if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res->getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -485,8 +466,6 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, out_values += STYLE_NUM_ENTRIES; } - res->unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp new file mode 100644 index 000000000000..7c1ee5cd7cfa --- /dev/null +++ b/libs/androidfw/Idmap.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG ATRACE_TAG_RESOURCES + +#include "androidfw/Idmap.h" + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" +#include "utils/ByteOrder.h" +#include "utils/Trace.h" + +#ifdef _WIN32 +#ifdef ERROR +#undef ERROR +#endif +#endif + +#include "androidfw/ResourceTypes.h" + +using ::android::base::StringPrintf; + +namespace android { + +constexpr static inline bool is_valid_package_id(uint16_t id) { + return id != 0 && id <= 255; +} + +constexpr static inline bool is_valid_type_id(uint16_t id) { + // Type IDs and package IDs have the same constraints in the IDMAP. + return is_valid_package_id(id); +} + +bool LoadedIdmap::Lookup(const IdmapEntry_header* header, uint16_t input_entry_id, + uint16_t* output_entry_id) { + if (input_entry_id < dtohs(header->entry_id_offset)) { + // After applying the offset, the entry is not present. + return false; + } + + input_entry_id -= dtohs(header->entry_id_offset); + if (input_entry_id >= dtohs(header->entry_count)) { + // The entry is not present. + return false; + } + + uint32_t result = dtohl(header->entries[input_entry_id]); + if (result == 0xffffffffu) { + return false; + } + *output_entry_id = static_cast<uint16_t>(result); + return true; +} + +static bool is_word_aligned(const void* data) { + return (reinterpret_cast<uintptr_t>(data) & 0x03) == 0; +} + +static bool IsValidIdmapHeader(const StringPiece& data) { + if (!is_word_aligned(data.data())) { + LOG(ERROR) << "Idmap header is not word aligned."; + return false; + } + + if (data.size() < sizeof(Idmap_header)) { + LOG(ERROR) << "Idmap header is too small."; + return false; + } + + const Idmap_header* header = reinterpret_cast<const Idmap_header*>(data.data()); + if (dtohl(header->magic) != kIdmapMagic) { + LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)", + dtohl(header->magic), kIdmapMagic); + return false; + } + + if (dtohl(header->version) != kIdmapCurrentVersion) { + // We are strict about versions because files with this format are auto-generated and don't need + // backwards compatibility. + LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)", + dtohl(header->version), kIdmapCurrentVersion); + return false; + } + + if (!is_valid_package_id(dtohs(header->target_package_id))) { + LOG(ERROR) << StringPrintf("Target package ID in Idmap is invalid: 0x%02x", + dtohs(header->target_package_id)); + return false; + } + + if (dtohs(header->type_count) > 255) { + LOG(ERROR) << StringPrintf("Idmap has too many type mappings (was %d, max 255)", + (int)dtohs(header->type_count)); + return false; + } + return true; +} + +LoadedIdmap::LoadedIdmap(const Idmap_header* header) : header_(header) { + size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path), + arraysize(header_->overlay_path)); + overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length); +} + +std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) { + ATRACE_CALL(); + if (!IsValidIdmapHeader(idmap_data)) { + return {}; + } + + const Idmap_header* header = reinterpret_cast<const Idmap_header*>(idmap_data.data()); + + // Can't use make_unique because LoadedImpl constructor is private. + std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>(new LoadedIdmap(header)); + + const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + sizeof(*header); + size_t data_size = idmap_data.size() - sizeof(*header); + + size_t type_maps_encountered = 0u; + while (data_size >= sizeof(IdmapEntry_header)) { + if (!is_word_aligned(data_ptr)) { + LOG(ERROR) << "Type mapping in Idmap is not word aligned"; + return {}; + } + + // Validate the type IDs. + const IdmapEntry_header* entry_header = reinterpret_cast<const IdmapEntry_header*>(data_ptr); + if (!is_valid_type_id(dtohs(entry_header->target_type_id)) || !is_valid_type_id(dtohs(entry_header->overlay_type_id))) { + LOG(ERROR) << StringPrintf("Invalid type map (0x%02x -> 0x%02x)", + dtohs(entry_header->target_type_id), + dtohs(entry_header->overlay_type_id)); + return {}; + } + + // Make sure there is enough space for the entries declared in the header. + if ((data_size - sizeof(*entry_header)) / sizeof(uint32_t) < + static_cast<size_t>(dtohs(entry_header->entry_count))) { + LOG(ERROR) << StringPrintf("Idmap too small for the number of entries (%d)", + (int)dtohs(entry_header->entry_count)); + return {}; + } + + // Only add a non-empty overlay. + if (dtohs(entry_header->entry_count != 0)) { + loaded_idmap->type_map_[static_cast<uint8_t>(dtohs(entry_header->overlay_type_id))] = + entry_header; + } + + const size_t entry_size_bytes = + sizeof(*entry_header) + (dtohs(entry_header->entry_count) * sizeof(uint32_t)); + data_ptr += entry_size_bytes; + data_size -= entry_size_bytes; + type_maps_encountered++; + } + + // Verify that we parsed all the type maps. + if (type_maps_encountered != static_cast<size_t>(dtohs(header->type_count))) { + LOG(ERROR) << "Parsed " << type_maps_encountered << " type maps but expected " + << (int)dtohs(header->type_count); + return {}; + } + return std::move(loaded_idmap); +} + +uint8_t LoadedIdmap::TargetPackageId() const { + return static_cast<uint8_t>(dtohs(header_->target_package_id)); +} + +const IdmapEntry_header* LoadedIdmap::GetEntryMapForType(uint8_t type_id) const { + auto iter = type_map_.find(type_id); + if (iter != type_map_.end()) { + return iter->second; + } + return nullptr; +} + +} // namespace android diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index bd7b80469ddc..04d506a2d71c 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -18,6 +18,7 @@ #include "androidfw/LoadedArsc.h" +#include <algorithm> #include <cstddef> #include <limits> @@ -37,46 +38,12 @@ #include "androidfw/ResourceUtils.h" #include "androidfw/Util.h" -using android::base::StringPrintf; +using ::android::base::StringPrintf; namespace android { constexpr const static int kAppPackageId = 0x7f; -// Element of a TypeSpec array. See TypeSpec. -struct Type { - // The configuration for which this type defines entries. - // This is already converted to host endianness. - ResTable_config configuration; - - // Pointer to the mmapped data where entry definitions are kept. - const ResTable_type* type; -}; - -// TypeSpec is going to be immediately proceeded by -// an array of Type structs, all in the same block of memory. -struct TypeSpec { - // Pointer to the mmapped data where flags are kept. - // Flags denote whether the resource entry is public - // and under which configurations it varies. - const ResTable_typeSpec* type_spec; - - // The number of types that follow this struct. - // There is a type for each configuration - // that entries are defined for. - size_t type_count; - - // Trick to easily access a variable number of Type structs - // proceeding this struct, and to ensure their alignment. - const Type types[0]; -}; - -// TypeSpecPtr points to the block of memory that holds -// a TypeSpec struct, followed by an array of Type structs. -// TypeSpecPtr is a managed pointer that knows how to delete -// itself. -using TypeSpecPtr = util::unique_cptr<TypeSpec>; - namespace { // Builder that helps accumulate Type structs and then create a single @@ -84,23 +51,28 @@ namespace { // the Type structs. class TypeSpecPtrBuilder { public: - TypeSpecPtrBuilder(const ResTable_typeSpec* header) : header_(header) {} + explicit TypeSpecPtrBuilder(const ResTable_typeSpec* header, + const IdmapEntry_header* idmap_header) + : header_(header), idmap_header_(idmap_header) { + } void AddType(const ResTable_type* type) { - ResTable_config config; - config.copyFromDtoH(type->config); - types_.push_back(Type{config, type}); + types_.push_back(type); } TypeSpecPtr Build() { // Check for overflow. - if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) { + using ElementType = const ResTable_type*; + if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) < + types_.size()) { return {}; } - TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type))); + TypeSpec* type_spec = + (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType))); type_spec->type_spec = header_; + type_spec->idmap_entries = idmap_header_; type_spec->type_count = types_.size(); - memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type)); + memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType)); return TypeSpecPtr(type_spec); } @@ -108,218 +80,186 @@ class TypeSpecPtrBuilder { DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder); const ResTable_typeSpec* header_; - std::vector<Type> types_; + const IdmapEntry_header* idmap_header_; + std::vector<const ResTable_type*> types_; }; } // namespace -bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - LoadedArscEntry* out_entry, ResTable_config* out_selected_config, - uint32_t* out_flags) const { - ATRACE_CALL(); +LoadedPackage::LoadedPackage() = default; +LoadedPackage::~LoadedPackage() = default; - // If the type IDs are offset in this package, we need to take that into account when searching - // for a type. - const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_]; - if (ptr == nullptr) { +// Precondition: The header passed in has already been verified, so reading any fields and trusting +// the ResChunk_header is safe. +static bool VerifyResTableType(const ResTable_type* header) { + if (header->id == 0) { + LOG(ERROR) << "RES_TABLE_TYPE_TYPE has invalid ID 0."; return false; } - // Don't bother checking if the entry ID is larger than - // the number of entries. - if (entry_idx >= dtohl(ptr->type_spec->entryCount)) { + const size_t entry_count = dtohl(header->entryCount); + if (entry_count > std::numeric_limits<uint16_t>::max()) { + LOG(ERROR) << "RES_TABLE_TYPE_TYPE has too many entries (" << entry_count << ")."; return false; } - const ResTable_config* best_config = nullptr; - const ResTable_type* best_type = nullptr; - uint32_t best_offset = 0; - - for (uint32_t i = 0; i < ptr->type_count; i++) { - const Type* type = &ptr->types[i]; - - if (type->configuration.match(config) && - (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) { - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - size_t entry_count = dtohl(type->type->entryCount); - if (entry_idx < entry_count) { - const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize)); - const uint32_t offset = dtohl(entry_offsets[entry_idx]); - if (offset != ResTable_type::NO_ENTRY) { - // There is an entry for this resource, record it. - best_config = &type->configuration; - best_type = type->type; - best_offset = offset + dtohl(type->type->entriesStart); - } - } - } - } + // Make sure that there is enough room for the entry offsets. + const size_t offsets_offset = dtohs(header->header.headerSize); + const size_t entries_offset = dtohl(header->entriesStart); + const size_t offsets_length = sizeof(uint32_t) * entry_count; - if (best_type == nullptr) { + if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) { + LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data."; return false; } - const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1); - *out_flags = dtohl(flags[entry_idx]); - *out_selected_config = *best_config; - - const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>( - reinterpret_cast<const uint8_t*>(best_type) + best_offset); - out_entry->entry = best_entry; - out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1); - out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index)); - return true; -} - -// The destructor gets generated into arbitrary translation units -// if left implicit, which causes the compiler to complain about -// forward declarations and incomplete types. -LoadedArsc::~LoadedArsc() {} - -bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, - LoadedArscEntry* out_entry, ResTable_config* out_selected_config, - uint32_t* out_flags) const { - ATRACE_CALL(); - const uint8_t package_id = get_package_id(resid); - const uint8_t type_id = get_type_id(resid); - const uint16_t entry_id = get_entry_id(resid); - - if (type_id == 0) { - LOG(ERROR) << "Invalid ID 0x" << std::hex << resid << std::dec << "."; + if (entries_offset > dtohl(header->header.size)) { + LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets extend beyond chunk."; return false; } - for (const auto& loaded_package : packages_) { - if (loaded_package->package_id_ == package_id) { - return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry, - out_selected_config, out_flags); - } + if (entries_offset & 0x03) { + LOG(ERROR) << "RES_TABLE_TYPE_TYPE entries start at unaligned address."; + return false; } - return false; + return true; } -const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const { - const uint8_t package_id = get_package_id(resid); - for (const auto& loaded_package : packages_) { - if (loaded_package->package_id_ == package_id) { - return loaded_package.get(); - } +static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) { + // Check that the offset is aligned. + if (entry_offset & 0x03) { + LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned."; + return false; } - return nullptr; -} -static bool VerifyType(const Chunk& chunk) { - ATRACE_CALL(); - const ResTable_type* header = chunk.header<ResTable_type, kResTableTypeMinSize>(); - - const size_t entry_count = dtohl(header->entryCount); - if (entry_count > std::numeric_limits<uint16_t>::max()) { - LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE."; + // Check that the offset doesn't overflow. + if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) { + // Overflow in offset. + LOG(ERROR) << "Entry at offset " << entry_offset << " is too large."; return false; } - // Make sure that there is enough room for the entry offsets. - const size_t offsets_offset = chunk.header_size(); - const size_t entries_offset = dtohl(header->entriesStart); - const size_t offsets_length = sizeof(uint32_t) * entry_count; + const size_t chunk_size = dtohl(type->header.size); - if (offsets_offset + offsets_length > entries_offset) { - LOG(ERROR) << "Entry offsets overlap actual entry data."; + entry_offset += dtohl(type->entriesStart); + if (entry_offset > chunk_size - sizeof(ResTable_entry)) { + LOG(ERROR) << "Entry at offset " << entry_offset + << " is too large. No room for ResTable_entry."; return false; } - if (entries_offset > chunk.size()) { - LOG(ERROR) << "Entry offsets extend beyond chunk."; + const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( + reinterpret_cast<const uint8_t*>(type) + entry_offset); + + const size_t entry_size = dtohs(entry->size); + if (entry_size < sizeof(*entry)) { + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset + << " is too small."; return false; } - if (entries_offset & 0x03) { - LOG(ERROR) << "Entries start at unaligned address."; + if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) { + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset + << " is too large."; return false; } - // Check each entry offset. - const uint32_t* offsets = - reinterpret_cast<const uint32_t*>(reinterpret_cast<const uint8_t*>(header) + offsets_offset); - for (size_t i = 0; i < entry_count; i++) { - uint32_t offset = dtohl(offsets[i]); - if (offset != ResTable_type::NO_ENTRY) { - // Check that the offset is aligned. - if (offset & 0x03) { - LOG(ERROR) << "Entry offset at index " << i << " is not 4-byte aligned."; - return false; - } - - // Check that the offset doesn't overflow. - if (offset > std::numeric_limits<uint32_t>::max() - entries_offset) { - // Overflow in offset. - LOG(ERROR) << "Entry offset at index " << i << " is too large."; - return false; - } + if (entry_size < sizeof(ResTable_map_entry)) { + // There needs to be room for one Res_value struct. + if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) { + LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset + << " for type " << (int)type->id << "."; + return false; + } - offset += entries_offset; - if (offset > chunk.size() - sizeof(ResTable_entry)) { - LOG(ERROR) << "Entry offset at index " << i << " is too large. No room for ResTable_entry."; - return false; - } + const Res_value* value = + reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size); + const size_t value_size = dtohs(value->size); + if (value_size < sizeof(Res_value)) { + LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small."; + return false; + } - const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( - reinterpret_cast<const uint8_t*>(header) + offset); - const size_t entry_size = dtohs(entry->size); - if (entry_size < sizeof(*entry)) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " is too small."; - return false; - } + if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) { + LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset + << " is too large."; + return false; + } + } else { + const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry); + const size_t map_entry_count = dtohl(map->count); + size_t map_entries_start = entry_offset + entry_size; + if (map_entries_start & 0x03) { + LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset."; + return false; + } - // Check the declared entrySize. - if (entry_size > chunk.size() || offset > chunk.size() - entry_size) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " is too large."; - return false; - } + // Each entry is sizeof(ResTable_map) big. + if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) { + LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << "."; + return false; + } + } + return true; +} - // If this is a map entry, then keep validating. - if (entry_size >= sizeof(ResTable_map_entry)) { - const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry); - const size_t map_entry_count = dtohl(map->count); +const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk, + uint16_t entry_index) { + uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index); + if (entry_offset == ResTable_type::NO_ENTRY) { + return nullptr; + } + return GetEntryFromOffset(type_chunk, entry_offset); +} - size_t map_entries_start = offset + entry_size; - if (map_entries_start & 0x03) { - LOG(ERROR) << "Map entries start at unaligned offset."; - return false; - } +uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const size_t entry_count = dtohl(type_chunk->entryCount); + const size_t offsets_offset = dtohs(type_chunk->header.headerSize); + + // Check if there is the desired entry in this type. + + if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { + // This is encoded as a sparse map, so perform a binary search. + const ResTable_sparseTypeEntry* sparse_indices = + reinterpret_cast<const ResTable_sparseTypeEntry*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; + const ResTable_sparseTypeEntry* result = + std::lower_bound(sparse_indices, sparse_indices_end, entry_index, + [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) { + return dtohs(entry.idx) < entry_idx; + }); + + if (result == sparse_indices_end || dtohs(result->idx) != entry_index) { + // No entry found. + return ResTable_type::NO_ENTRY; + } - // Each entry is sizeof(ResTable_map) big. - if (map_entry_count > ((chunk.size() - map_entries_start) / sizeof(ResTable_map))) { - LOG(ERROR) << "Too many map entries in ResTable_map_entry."; - return false; - } + // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as + // the real offset divided by 4. + return uint32_t{dtohs(result->offset)} * 4u; + } - // Great, all the map entries fit!. - } else { - // There needs to be room for one Res_value struct. - if (offset + entry_size > chunk.size() - sizeof(Res_value)) { - LOG(ERROR) << "No room for Res_value after ResTable_entry."; - return false; - } + // This type is encoded as a dense array. + if (entry_index >= entry_count) { + // This entry cannot be here. + return ResTable_type::NO_ENTRY; + } - const Res_value* value = reinterpret_cast<const Res_value*>( - reinterpret_cast<const uint8_t*>(entry) + entry_size); - const size_t value_size = dtohs(value->size); - if (value_size < sizeof(Res_value)) { - LOG(ERROR) << "Res_value is too small."; - return false; - } + const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + return dtohl(entry_offsets[entry_index]); +} - if (value_size > chunk.size() || offset + entry_size > chunk.size() - value_size) { - LOG(ERROR) << "Res_value size is too large."; - return false; - } - } - } +const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk, + uint32_t offset) { + if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) { + return nullptr; } - return true; + return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) + + offset + dtohl(type_chunk->entriesStart)); } void LoadedPackage::CollectConfigurations(bool exclude_mipmap, @@ -327,7 +267,7 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, const static std::u16string kMipMap = u"mipmap"; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { if (exclude_mipmap) { const int type_idx = type_spec->type_spec->id - 1; @@ -348,8 +288,11 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, } } - for (size_t j = 0; j < type_spec->type_count; j++) { - out_configs->insert(type_spec->types[j].configuration); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config config; + config.copyFromDtoH((*iter)->config); + out_configs->insert(config); } } } @@ -359,10 +302,12 @@ void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out char temp_locale[RESTABLE_MAX_LOCALE_LEN]; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { - for (size_t j = 0; j < type_spec->type_count; j++) { - const ResTable_config& configuration = type_spec->types[j].configuration; + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config configuration; + configuration.copyFromDtoH((*iter)->config); if (configuration.locale != 0) { configuration.getBcp47Locale(temp_locale, canonicalize); std::string locale(temp_locale); @@ -390,17 +335,17 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } - for (size_t ti = 0; ti < type_spec->type_count; ti++) { - const Type* type = &type_spec->types[ti]; - size_t entry_count = dtohl(type->type->entryCount); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + const ResTable_type* type = *iter; + size_t entry_count = dtohl(type->entryCount); for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize)); + reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize)); const uint32_t offset = dtohl(entry_offsets[entry_idx]); if (offset != ResTable_type::NO_ENTRY) { - const ResTable_entry* entry = - reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) + - dtohl(type->type->entriesStart) + offset); + const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( + reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset); if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) { // The package ID will be overridden by the caller (due to runtime assignment of package // IDs for shared libraries). @@ -412,28 +357,50 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } -std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { - ATRACE_CALL(); - std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()}; +const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const { + for (const auto& loaded_package : packages_) { + if (loaded_package->GetPackageId() == package_id) { + return loaded_package.get(); + } + } + return nullptr; +} + +std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, + const LoadedIdmap* loaded_idmap, + bool system, bool load_as_shared_library) { + ATRACE_NAME("LoadedPackage::Load"); + std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage()); + // typeIdOffset was added at some point, but we still must recognize apps built before this + // was added. constexpr size_t kMinPackageSize = sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset); const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>(); if (header == nullptr) { - LOG(ERROR) << "Chunk RES_TABLE_PACKAGE_TYPE is too small."; + LOG(ERROR) << "RES_TABLE_PACKAGE_TYPE too small."; return {}; } + loaded_package->system_ = system; + loaded_package->package_id_ = dtohl(header->id); - if (loaded_package->package_id_ == 0) { + if (loaded_package->package_id_ == 0 || + (loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) { // Package ID of 0 means this is a shared library. loaded_package->dynamic_ = true; } + if (loaded_idmap != nullptr) { + // This is an overlay and so it needs to pretend to be the target package. + loaded_package->package_id_ = loaded_idmap->TargetPackageId(); + loaded_package->overlay_ = true; + } + if (header->header.headerSize >= sizeof(ResTable_package)) { uint32_t type_id_offset = dtohl(header->typeIdOffset); if (type_id_offset > std::numeric_limits<uint8_t>::max()) { - LOG(ERROR) << "Type ID offset in RES_TABLE_PACKAGE_TYPE is too large."; + LOG(ERROR) << "RES_TABLE_PACKAGE_TYPE type ID offset too large."; return {}; } loaded_package->type_id_offset_ = static_cast<int>(type_id_offset); @@ -442,14 +409,10 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { util::ReadUtf16StringFromDevice(header->name, arraysize(header->name), &loaded_package->package_name_); - // A TypeSpec builder. We use this to accumulate the set of Types - // available for a TypeSpec, and later build a single, contiguous block - // of memory that holds all the Types together with the TypeSpec. - std::unique_ptr<TypeSpecPtrBuilder> types_builder; - - // Keep track of the last seen type index. Since type IDs are 1-based, - // this records their index, which is 0-based (type ID - 1). - uint8_t last_type_idx = 0; + // A map of TypeSpec builders, each associated with an type index. + // We use these to accumulate the set of Types available for a TypeSpec, and later build a single, + // contiguous block of memory that holds all the Types together with the TypeSpec. + std::unordered_map<int, std::unique_ptr<TypeSpecPtrBuilder>> type_builder_map; ChunkIterator iter(chunk.data_ptr(), chunk.data_size()); while (iter.HasNext()) { @@ -464,7 +427,7 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { status_t err = loaded_package->type_string_pool_.setTo( child_chunk.header<ResStringPool_header>(), child_chunk.size()); if (err != NO_ERROR) { - LOG(ERROR) << "Corrupt package type string pool."; + LOG(ERROR) << "RES_STRING_POOL_TYPE for types corrupt."; return {}; } } else if (pool_address == header_address + dtohl(header->keyStrings)) { @@ -472,44 +435,29 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { status_t err = loaded_package->key_string_pool_.setTo( child_chunk.header<ResStringPool_header>(), child_chunk.size()); if (err != NO_ERROR) { - LOG(ERROR) << "Corrupt package key string pool."; + LOG(ERROR) << "RES_STRING_POOL_TYPE for keys corrupt."; return {}; } } else { - LOG(WARNING) << "Too many string pool chunks found in package."; + LOG(WARNING) << "Too many RES_STRING_POOL_TYPEs found in RES_TABLE_PACKAGE_TYPE."; } } break; case RES_TABLE_TYPE_SPEC_TYPE: { - ATRACE_NAME("LoadTableTypeSpec"); - - // Starting a new TypeSpec, so finish the old one if there was one. - if (types_builder) { - TypeSpecPtr type_spec_ptr = types_builder->Build(); - if (type_spec_ptr == nullptr) { - LOG(ERROR) << "Too many type configurations, overflow detected."; - return {}; - } - loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr); - - types_builder = {}; - last_type_idx = 0; - } - const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>(); if (type_spec == nullptr) { - LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE is too small."; + LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small."; return {}; } if (type_spec->id == 0) { - LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0."; + LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0."; return {}; } if (loaded_package->type_id_offset_ + static_cast<int>(type_spec->id) > std::numeric_limits<uint8_t>::max()) { - LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has out of range ID."; + LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE has out of range ID."; return {}; } @@ -521,54 +469,63 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { // There can only be 2^16 entries in a type, because that is the ID // space for entries (EEEE) in the resource ID 0xPPTTEEEE. if (entry_count > std::numeric_limits<uint16_t>::max()) { - LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_SPEC_TYPE: " << entry_count << "."; + LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE has too many entries (" << entry_count << ")."; return {}; } if (entry_count * sizeof(uint32_t) > chunk.data_size()) { - LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_TYPE_SPEC_TYPE."; + LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries."; return {}; } - last_type_idx = type_spec->id - 1; - types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec); + // If this is an overlay, associate the mapping of this type to the target type + // from the IDMAP. + const IdmapEntry_header* idmap_entry_header = nullptr; + if (loaded_idmap != nullptr) { + idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id); + } + + std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1]; + if (builder_ptr == nullptr) { + builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header); + } else { + LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x", + type_spec->id); + } } break; case RES_TABLE_TYPE_TYPE: { const ResTable_type* type = child_chunk.header<ResTable_type, kResTableTypeMinSize>(); if (type == nullptr) { - LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE is too small."; + LOG(ERROR) << "RES_TABLE_TYPE_TYPE too small."; return {}; } - if (type->id == 0) { - LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE has invalid ID 0."; + if (!VerifyResTableType(type)) { return {}; } // Type chunks must be preceded by their TypeSpec chunks. - if (!types_builder || type->id - 1 != last_type_idx) { - LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without " - "RES_TABLE_TYPE_SPEC_TYPE."; - return {}; - } - - if (!VerifyType(child_chunk)) { + std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1]; + if (builder_ptr != nullptr) { + builder_ptr->AddType(type); + } else { + LOG(ERROR) << StringPrintf( + "RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.", + type->id); return {}; } - - types_builder->AddType(type); } break; case RES_TABLE_LIBRARY_TYPE: { const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>(); if (lib == nullptr) { - LOG(ERROR) << "Chunk RES_TABLE_LIBRARY_TYPE is too small."; + LOG(ERROR) << "RES_TABLE_LIBRARY_TYPE too small."; return {}; } if (child_chunk.data_size() / sizeof(ResTable_lib_entry) < dtohl(lib->count)) { - LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_LIBRARY_TYPE."; + LOG(ERROR) << "RES_TABLE_LIBRARY_TYPE too small to hold entries."; return {}; } @@ -583,7 +540,7 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { arraysize(entry_iter->packageName), &package_name); if (dtohl(entry_iter->packageId) >= std::numeric_limits<uint8_t>::max()) { - LOG(ERROR) << base::StringPrintf( + LOG(ERROR) << StringPrintf( "Package ID %02x in RES_TABLE_LIBRARY_TYPE too large for package '%s'.", dtohl(entry_iter->packageId), package_name.c_str()); return {}; @@ -596,33 +553,44 @@ std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) { } break; default: - LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type()); + LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); break; } } - // Finish the last TypeSpec. - if (types_builder) { - TypeSpecPtr type_spec_ptr = types_builder->Build(); + if (iter.HadError()) { + LOG(ERROR) << iter.GetLastError(); + return {}; + } + + // Flatten and construct the TypeSpecs. + for (auto& entry : type_builder_map) { + uint8_t type_idx = static_cast<uint8_t>(entry.first); + TypeSpecPtr type_spec_ptr = entry.second->Build(); if (type_spec_ptr == nullptr) { LOG(ERROR) << "Too many type configurations, overflow detected."; return {}; } - loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr); - } - if (iter.HadError()) { - LOG(ERROR) << iter.GetLastError(); - return {}; + // We only add the type to the package if there is no IDMAP, or if the type is + // overlaying something. + if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) { + // If this is an overlay, insert it at the target type ID. + if (type_spec_ptr->idmap_entries != nullptr) { + type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1; + } + loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr); + } } - return loaded_package; + + return std::move(loaded_package); } -bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) { - ATRACE_CALL(); +bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, + bool load_as_shared_library) { const ResTable_header* header = chunk.header<ResTable_header>(); if (header == nullptr) { - LOG(ERROR) << "Chunk RES_TABLE_TYPE is too small."; + LOG(ERROR) << "RES_TABLE_TYPE too small."; return false; } @@ -641,38 +609,32 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) { status_t err = global_string_pool_.setTo(child_chunk.header<ResStringPool_header>(), child_chunk.size()); if (err != NO_ERROR) { - LOG(ERROR) << "Corrupt string pool."; + LOG(ERROR) << "RES_STRING_POOL_TYPE corrupt."; return false; } } else { - LOG(WARNING) << "Multiple string pool chunks found in resource table."; + LOG(WARNING) << "Multiple RES_STRING_POOL_TYPEs found in RES_TABLE_TYPE."; } break; case RES_TABLE_PACKAGE_TYPE: { if (packages_seen + 1 > package_count) { LOG(ERROR) << "More package chunks were found than the " << package_count - << " declared in the " - "header."; + << " declared in the header."; return false; } packages_seen++; - std::unique_ptr<LoadedPackage> loaded_package = LoadedPackage::Load(child_chunk); + std::unique_ptr<const LoadedPackage> loaded_package = + LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library); if (!loaded_package) { return false; } - - // Mark the package as dynamic if we are forcefully loading the Apk as a shared library. - if (loaded_package->package_id_ == kAppPackageId) { - loaded_package->dynamic_ = load_as_shared_library; - } - loaded_package->system_ = system_; packages_.push_back(std::move(loaded_package)); } break; default: - LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type()); + LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); break; } } @@ -684,26 +646,27 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) { return true; } -std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const void* data, size_t len, bool system, +std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data, + const LoadedIdmap* loaded_idmap, bool system, bool load_as_shared_library) { - ATRACE_CALL(); + ATRACE_NAME("LoadedArsc::LoadTable"); // Not using make_unique because the constructor is private. std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc()); loaded_arsc->system_ = system; - ChunkIterator iter(data, len); + ChunkIterator iter(data.data(), data.size()); while (iter.HasNext()) { const Chunk chunk = iter.Next(); switch (chunk.type()) { case RES_TABLE_TYPE: - if (!loaded_arsc->LoadTable(chunk, load_as_shared_library)) { + if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) { return {}; } break; default: - LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type()); + LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); break; } } @@ -717,4 +680,8 @@ std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const void* data, size_t len, return std::move(loaded_arsc); } +std::unique_ptr<const LoadedArsc> LoadedArsc::CreateEmpty() { + return std::unique_ptr<LoadedArsc>(new LoadedArsc()); +} + } // namespace android diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index b184d12b674d..861dc0f3879c 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -456,6 +456,22 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) uninit(); + // The chunk must be at least the size of the string pool header. + if (size < sizeof(ResStringPool_header)) { + ALOGW("Bad string block: data size %zu is too small to be a string block", size); + return (mError=BAD_TYPE); + } + + // The data is at least as big as a ResChunk_header, so we can safely validate the other + // header fields. + // `data + size` is safe because the source of `size` comes from the kernel/filesystem. + if (validate_chunk(reinterpret_cast<const ResChunk_header*>(data), sizeof(ResStringPool_header), + reinterpret_cast<const uint8_t*>(data) + size, + "ResStringPool_header") != NO_ERROR) { + ALOGW("Bad string block: malformed block dimensions"); + return (mError=BAD_TYPE); + } + const bool notDeviceEndian = htods(0xf0) != 0xf0; if (copyData || notDeviceEndian) { @@ -467,6 +483,8 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) data = mOwnedData; } + // The size has been checked, so it is safe to read the data in the ResStringPool_header + // data structure. mHeader = (const ResStringPool_header*)data; if (notDeviceEndian) { @@ -727,44 +745,28 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) { AutoMutex lock(mDecodeLock); - if (mCache == NULL) { -#ifndef __ANDROID__ - if (kDebugStringPoolNoisy) { - ALOGI("CREATING STRING CACHE OF %zu bytes", - mHeader->stringCount*sizeof(char16_t**)); - } -#else - // We do not want to be in this case when actually running Android. - ALOGW("CREATING STRING CACHE OF %zu bytes", - static_cast<size_t>(mHeader->stringCount*sizeof(char16_t**))); -#endif - mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*)); - if (mCache == NULL) { - ALOGW("No memory trying to allocate decode cache table of %d bytes\n", - (int)(mHeader->stringCount*sizeof(char16_t**))); - return NULL; - } + if (mCache != NULL && mCache[idx] != NULL) { + return mCache[idx]; } - if (mCache[idx] != NULL) { - return mCache[idx]; + // Retrieve the actual length of the utf8 string if the + // encoded length was truncated + if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) { + return NULL; } + // Since AAPT truncated lengths longer than 0x7FFF, check + // that the bits that remain after truncation at least match + // the bits of the actual length ssize_t actualLen = utf8_to_utf16_length(u8str, u8len); - if (actualLen < 0 || (size_t)actualLen != *u16len) { + if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) { ALOGW("Bad string block: string #%lld decoded length is not correct " "%lld vs %llu\n", (long long)idx, (long long)actualLen, (long long)*u16len); return NULL; } - // Reject malformed (non null-terminated) strings - if (u8str[u8len] != 0x00) { - ALOGW("Bad string block: string #%d is not null-terminated", - (int)idx); - return NULL; - } - + *u16len = (size_t) actualLen; char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t)); if (!u16str) { ALOGW("No memory when trying to allocate decode cache for string #%d\n", @@ -772,10 +774,31 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const return NULL; } + utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); + + if (mCache == NULL) { +#ifndef __ANDROID__ + if (kDebugStringPoolNoisy) { + ALOGI("CREATING STRING CACHE OF %zu bytes", + mHeader->stringCount*sizeof(char16_t**)); + } +#else + // We do not want to be in this case when actually running Android. + ALOGW("CREATING STRING CACHE OF %zu bytes", + static_cast<size_t>(mHeader->stringCount*sizeof(char16_t**))); +#endif + mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*)); + if (mCache == NULL) { + ALOGW("No memory trying to allocate decode cache table of %d bytes\n", + (int)(mHeader->stringCount*sizeof(char16_t**))); + return NULL; + } + } + if (kDebugStringPoolNoisy) { - ALOGI("Caching UTF8 string: %s", u8str); + ALOGI("Caching UTF8 string: %s", u8str); } - utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); + mCache[idx] = u16str; return u16str; } else { @@ -812,7 +835,8 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const *outLen = encLen; if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { - return (const char*)str; + return stringDecodeAt(idx, str, encLen, outLen); + } else { ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize); @@ -826,6 +850,38 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const return NULL; } +/** + * AAPT incorrectly writes a truncated string length when the string size + * exceeded the maximum possible encode length value (0x7FFF). To decode a + * truncated length, iterate through length values that end in the encode length + * bits. Strings that exceed the maximum encode length are not placed into + * StringPools in AAPT2. + **/ +const char* ResStringPool::stringDecodeAt(size_t idx, const uint8_t* str, + const size_t encLen, size_t* outLen) const { + const uint8_t* strings = (uint8_t*)mStrings; + + size_t i = 0, end = encLen; + while ((uint32_t)(str+end-strings) < mStringPoolSize) { + if (str[end] == 0x00) { + if (i != 0) { + ALOGW("Bad string block: string #%d is truncated (actual length is %d)", + (int)idx, (int)end); + } + + *outLen = end; + return (const char*)str; + } + + end = (++i << (sizeof(uint8_t) * 8 * 2 - 1)) | encLen; + } + + // Reject malformed (non null-terminated) strings + ALOGW("Bad string block: string #%d is not null-terminated", + (int)idx); + return NULL; +} + const String8 ResStringPool::string8ObjectAt(size_t idx) const { size_t len; @@ -1541,7 +1597,8 @@ static volatile int32_t gCount = 0; ResXMLTree::ResXMLTree(const DynamicRefTable* dynamicRefTable) : ResXMLParser(*this) - , mDynamicRefTable(dynamicRefTable) + , mDynamicRefTable((dynamicRefTable != nullptr) ? dynamicRefTable->clone() + : std::unique_ptr<DynamicRefTable>(nullptr)) , mError(NO_INIT), mOwnedData(NULL) { if (kDebugResXMLTree) { @@ -1552,7 +1609,7 @@ ResXMLTree::ResXMLTree(const DynamicRefTable* dynamicRefTable) ResXMLTree::ResXMLTree() : ResXMLParser(*this) - , mDynamicRefTable(NULL) + , mDynamicRefTable(std::unique_ptr<DynamicRefTable>(nullptr)) , mError(NO_INIT), mOwnedData(NULL) { if (kDebugResXMLTree) { @@ -1868,52 +1925,76 @@ void ResTable_config::swapHtoD() { /* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) { if (l.locale != r.locale) { - // NOTE: This is the old behaviour with respect to comparison orders. - // The diff value here doesn't make much sense (given our bit packing scheme) - // but it's stable, and that's all we need. - return l.locale - r.locale; + return (l.locale > r.locale) ? 1 : -1; } - // The language & region are equal, so compare the scripts and variants. + // The language & region are equal, so compare the scripts, variants and + // numbering systms in this order. Comparison of variants and numbering + // systems should happen very infrequently (if at all.) + // The comparison code relies on memcmp low-level optimizations that make it + // more efficient than strncmp. const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript; const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript; + int script = memcmp(lScript, rScript, sizeof(l.localeScript)); if (script) { return script; } - // The language, region and script are equal, so compare variants. - // - // This should happen very infrequently (if at all.) - return memcmp(l.localeVariant, r.localeVariant, sizeof(l.localeVariant)); + int variant = memcmp(l.localeVariant, r.localeVariant, sizeof(l.localeVariant)); + if (variant) { + return variant; + } + + return memcmp(l.localeNumberingSystem, r.localeNumberingSystem, + sizeof(l.localeNumberingSystem)); } int ResTable_config::compare(const ResTable_config& o) const { - int32_t diff = (int32_t)(imsi - o.imsi); - if (diff != 0) return diff; - diff = compareLocales(*this, o); - if (diff != 0) return diff; - diff = (int32_t)(screenType - o.screenType); - if (diff != 0) return diff; - diff = (int32_t)(input - o.input); - if (diff != 0) return diff; - diff = (int32_t)(screenSize - o.screenSize); - if (diff != 0) return diff; - diff = (int32_t)(version - o.version); - if (diff != 0) return diff; - diff = (int32_t)(screenLayout - o.screenLayout); - if (diff != 0) return diff; - diff = (int32_t)(screenLayout2 - o.screenLayout2); - if (diff != 0) return diff; - diff = (int32_t)(colorMode - o.colorMode); - if (diff != 0) return diff; - diff = (int32_t)(uiMode - o.uiMode); - if (diff != 0) return diff; - diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp); - if (diff != 0) return diff; - diff = (int32_t)(screenSizeDp - o.screenSizeDp); - return (int)diff; + if (imsi != o.imsi) { + return (imsi > o.imsi) ? 1 : -1; + } + + int32_t diff = compareLocales(*this, o); + if (diff < 0) { + return -1; + } + if (diff > 0) { + return 1; + } + + if (screenType != o.screenType) { + return (screenType > o.screenType) ? 1 : -1; + } + if (input != o.input) { + return (input > o.input) ? 1 : -1; + } + if (screenSize != o.screenSize) { + return (screenSize > o.screenSize) ? 1 : -1; + } + if (version != o.version) { + return (version > o.version) ? 1 : -1; + } + if (screenLayout != o.screenLayout) { + return (screenLayout > o.screenLayout) ? 1 : -1; + } + if (screenLayout2 != o.screenLayout2) { + return (screenLayout2 > o.screenLayout2) ? 1 : -1; + } + if (colorMode != o.colorMode) { + return (colorMode > o.colorMode) ? 1 : -1; + } + if (uiMode != o.uiMode) { + return (uiMode > o.uiMode) ? 1 : -1; + } + if (smallestScreenWidthDp != o.smallestScreenWidthDp) { + return (smallestScreenWidthDp > o.smallestScreenWidthDp) ? 1 : -1; + } + if (screenSizeDp != o.screenSizeDp) { + return (screenSizeDp > o.screenSizeDp) ? 1 : -1; + } + return 0; } int ResTable_config::compareLogical(const ResTable_config& o) const { @@ -2008,6 +2089,22 @@ int ResTable_config::diff(const ResTable_config& o) const { return diffs; } +// There isn't a well specified "importance" order between variants and +// scripts. We can't easily tell whether, say "en-Latn-US" is more or less +// specific than "en-US-POSIX". +// +// We therefore arbitrarily decide to give priority to variants over +// scripts since it seems more useful to do so. We will consider +// "en-US-POSIX" to be more specific than "en-Latn-US". +// +// Unicode extension keywords are considered to be less important than +// scripts and variants. +inline int ResTable_config::getImportanceScoreOfLocale() const { + return (localeVariant[0] ? 4 : 0) + + (localeScript[0] && !localeScriptWasComputed ? 2: 0) + + (localeNumberingSystem[0] ? 1: 0); +} + int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const { if (locale || o.locale) { if (language[0] != o.language[0]) { @@ -2021,21 +2118,7 @@ int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const { } } - // There isn't a well specified "importance" order between variants and - // scripts. We can't easily tell whether, say "en-Latn-US" is more or less - // specific than "en-US-POSIX". - // - // We therefore arbitrarily decide to give priority to variants over - // scripts since it seems more useful to do so. We will consider - // "en-US-POSIX" to be more specific than "en-Latn-US". - - const int score = ((localeScript[0] != '\0' && !localeScriptWasComputed) ? 1 : 0) + - ((localeVariant[0] != '\0') ? 2 : 0); - - const int oScore = (o.localeScript[0] != '\0' && !o.localeScriptWasComputed ? 1 : 0) + - ((o.localeVariant[0] != '\0') ? 2 : 0); - - return score - oScore; + return getImportanceScoreOfLocale() - o.getImportanceScoreOfLocale(); } bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const { @@ -2292,6 +2375,17 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, return localeMatches; } + // The variants are the same, try numbering system. + const bool localeNumsysMatches = strncmp(localeNumberingSystem, + requested->localeNumberingSystem, + sizeof(localeNumberingSystem)) == 0; + const bool otherNumsysMatches = strncmp(o.localeNumberingSystem, + requested->localeNumberingSystem, + sizeof(localeNumberingSystem)) == 0; + if (localeNumsysMatches != otherNumsysMatches) { + return localeNumsysMatches; + } + // Finally, the languages, although equivalent, may still be different // (like for Tagalog and Filipino). Identical is better than just // equivalent. @@ -2759,7 +2853,7 @@ void ResTable_config::appendDirLocale(String8& out) const { return; } const bool scriptWasProvided = localeScript[0] != '\0' && !localeScriptWasComputed; - if (!scriptWasProvided && !localeVariant[0]) { + if (!scriptWasProvided && !localeVariant[0] && !localeNumberingSystem[0]) { // Legacy format. if (out.size() > 0) { out.append("-"); @@ -2804,6 +2898,12 @@ void ResTable_config::appendDirLocale(String8& out) const { out.append("+"); out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant))); } + + if (localeNumberingSystem[0]) { + out.append("+u+nu+"); + out.append(localeNumberingSystem, + strnlen(localeNumberingSystem, sizeof(localeNumberingSystem))); + } } void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool canonicalize) const { @@ -2846,15 +2946,119 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool can str[charsWritten++] = '-'; } memcpy(str + charsWritten, localeVariant, sizeof(localeVariant)); - } -} + charsWritten += strnlen(str + charsWritten, sizeof(localeVariant)); + } + + // Add Unicode extension only if at least one other locale component is present + if (localeNumberingSystem[0] != '\0' && charsWritten > 0) { + static constexpr char NU_PREFIX[] = "-u-nu-"; + static constexpr size_t NU_PREFIX_LEN = sizeof(NU_PREFIX) - 1; + memcpy(str + charsWritten, NU_PREFIX, NU_PREFIX_LEN); + charsWritten += NU_PREFIX_LEN; + memcpy(str + charsWritten, localeNumberingSystem, sizeof(localeNumberingSystem)); + } +} + +struct LocaleParserState { + enum State : uint8_t { + BASE, UNICODE_EXTENSION, IGNORE_THE_REST + } parserState; + enum UnicodeState : uint8_t { + /* Initial state after the Unicode singleton is detected. Either a keyword + * or an attribute is expected. */ + NO_KEY, + /* Unicode extension key (but not attribute) is expected. Next states: + * NO_KEY, IGNORE_KEY or NUMBERING_SYSTEM. */ + EXPECT_KEY, + /* A key is detected, however it is not supported for now. Ignore its + * value. Next states: IGNORE_KEY or NUMBERING_SYSTEM. */ + IGNORE_KEY, + /* Numbering system key was detected. Store its value in the configuration + * localeNumberingSystem field. Next state: EXPECT_KEY */ + NUMBERING_SYSTEM + } unicodeState; + + LocaleParserState(): parserState(BASE), unicodeState(NO_KEY) {} +}; -/* static */ inline bool assignLocaleComponent(ResTable_config* config, - const char* start, size_t size) { +/* static */ inline LocaleParserState assignLocaleComponent(ResTable_config* config, + const char* start, size_t size, LocaleParserState state) { + + /* It is assumed that this function is not invoked with state.parserState + * set to IGNORE_THE_REST. The condition is checked by setBcp47Locale + * function. */ + + if (state.parserState == LocaleParserState::UNICODE_EXTENSION) { + switch (size) { + case 1: + /* Other BCP 47 extensions are not supported at the moment */ + state.parserState = LocaleParserState::IGNORE_THE_REST; + break; + case 2: + if (state.unicodeState == LocaleParserState::NO_KEY || + state.unicodeState == LocaleParserState::EXPECT_KEY) { + /* Analyze Unicode extension key. Currently only 'nu' + * (numbering system) is supported.*/ + if ((start[0] == 'n' || start[0] == 'N') && + (start[1] == 'u' || start[1] == 'U')) { + state.unicodeState = LocaleParserState::NUMBERING_SYSTEM; + } else { + state.unicodeState = LocaleParserState::IGNORE_KEY; + } + } else { + /* Keys are not allowed in other state allowed, ignore the rest. */ + state.parserState = LocaleParserState::IGNORE_THE_REST; + } + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + switch (state.unicodeState) { + case LocaleParserState::NUMBERING_SYSTEM: + /* Accept only the first occurrence of the numbering system. */ + if (config->localeNumberingSystem[0] == '\0') { + for (size_t i = 0; i < size; ++i) { + config->localeNumberingSystem[i] = tolower(start[i]); + } + state.unicodeState = LocaleParserState::EXPECT_KEY; + } else { + state.parserState = LocaleParserState::IGNORE_THE_REST; + } + break; + case LocaleParserState::IGNORE_KEY: + /* Unsupported Unicode keyword. Ignore. */ + state.unicodeState = LocaleParserState::EXPECT_KEY; + break; + case LocaleParserState::EXPECT_KEY: + /* A keyword followed by an attribute is not allowed. */ + state.parserState = LocaleParserState::IGNORE_THE_REST; + break; + case LocaleParserState::NO_KEY: + /* Extension attribute. Do nothing. */ + break; + default: + break; + } + break; + default: + /* Unexpected field length - ignore the rest and treat as an error */ + state.parserState = LocaleParserState::IGNORE_THE_REST; + } + return state; + } switch (size) { case 0: - return false; + state.parserState = LocaleParserState::IGNORE_THE_REST; + break; + case 1: + state.parserState = (start[0] == 'u' || start[0] == 'U') + ? LocaleParserState::UNICODE_EXTENSION + : LocaleParserState::IGNORE_THE_REST; + break; case 2: case 3: config->language[0] ? config->packRegion(start) : config->packLanguage(start); @@ -2878,30 +3082,32 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool can } break; default: - return false; + state.parserState = LocaleParserState::IGNORE_THE_REST; } - return true; + return state; } void ResTable_config::setBcp47Locale(const char* in) { - locale = 0; - memset(localeScript, 0, sizeof(localeScript)); - memset(localeVariant, 0, sizeof(localeVariant)); + clearLocale(); - const char* separator = in; const char* start = in; - while ((separator = strchr(start, '-')) != NULL) { + LocaleParserState state; + while (const char* separator = strchr(start, '-')) { const size_t size = separator - start; - if (!assignLocaleComponent(this, start, size)) { - fprintf(stderr, "Invalid BCP-47 locale string: %s", in); + state = assignLocaleComponent(this, start, size, state); + if (state.parserState == LocaleParserState::IGNORE_THE_REST) { + fprintf(stderr, "Invalid BCP-47 locale string: %s\n", in); + break; } - start = (separator + 1); } - const size_t size = in + strlen(in) - start; - assignLocaleComponent(this, start, size); + if (state.parserState != LocaleParserState::IGNORE_THE_REST) { + const size_t size = strlen(start); + assignLocaleComponent(this, start, size, state); + } + localeScriptWasComputed = (localeScript[0] == '\0'); if (localeScriptWasComputed) { computeScript(); @@ -3299,13 +3505,14 @@ struct ResTable::PackageGroup { PackageGroup( ResTable* _owner, const String16& _name, uint32_t _id, - bool appAsLib, bool _isSystemAsset) + bool appAsLib, bool _isSystemAsset, bool _isDynamic) : owner(_owner) , name(_name) , id(_id) , largestTypeId(0) , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib) , isSystemAsset(_isSystemAsset) + , isDynamic(_isDynamic) { } ~PackageGroup() { @@ -3409,6 +3616,7 @@ struct ResTable::PackageGroup // If the package group comes from a system asset. Used in // determining non-system locales. const bool isSystemAsset; + const bool isDynamic; }; ResTable::Theme::Theme(const ResTable& table) @@ -3777,6 +3985,11 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1; } +inline ssize_t ResTable::getResourcePackageIndexFromPackage(uint8_t packageID) const +{ + return ((ssize_t)mPackageMap[packageID])-1; +} + status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) { return addInternal(data, size, NULL, 0, false, cookie, copyData); } @@ -3832,7 +4045,7 @@ status_t ResTable::add(ResTable* src, bool isSystemAsset) for (size_t i=0; i < src->mPackageGroups.size(); i++) { PackageGroup* srcPg = src->mPackageGroups[i]; PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, - false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset); + false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset, srcPg->isDynamic); for (size_t j=0; j<srcPg->packages.size(); j++) { pg->packages.add(srcPg->packages[j]); } @@ -6014,9 +6227,6 @@ void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales, StringPoolRef::StringPoolRef(const ResStringPool* pool, uint32_t index) : mPool(pool), mIndex(index) {} -StringPoolRef::StringPoolRef() - : mPool(NULL), mIndex(0) {} - const char* StringPoolRef::string8(size_t* outLen) const { if (mPool != NULL) { return mPool->string8At(mIndex, outLen); @@ -6075,6 +6285,68 @@ bool ResTable::getResourceFlags(uint32_t resID, uint32_t* outFlags) const { return true; } +bool ResTable::isPackageDynamic(uint8_t packageID) const { + if (mError != NO_ERROR) { + return false; + } + if (packageID == 0) { + ALOGW("Invalid package number 0x%08x", packageID); + return false; + } + + const ssize_t p = getResourcePackageIndexFromPackage(packageID); + + if (p < 0) { + ALOGW("Unknown package number 0x%08x", packageID); + return false; + } + + const PackageGroup* const grp = mPackageGroups[p]; + if (grp == NULL) { + ALOGW("Bad identifier for package number 0x%08x", packageID); + return false; + } + + return grp->isDynamic; +} + +bool ResTable::isResourceDynamic(uint32_t resID) const { + if (mError != NO_ERROR) { + return false; + } + + const ssize_t p = getResourcePackageIndex(resID); + const int t = Res_GETTYPE(resID); + const int e = Res_GETENTRY(resID); + + if (p < 0) { + if (Res_GETPACKAGE(resID)+1 == 0) { + ALOGW("No package identifier for resource number 0x%08x", resID); + } else { + ALOGW("No known package for resource number 0x%08x", resID); + } + return false; + } + if (t < 0) { + ALOGW("No type identifier for resource number 0x%08x", resID); + return false; + } + + const PackageGroup* const grp = mPackageGroups[p]; + if (grp == NULL) { + ALOGW("Bad identifier for resource number 0x%08x", resID); + return false; + } + + Entry entry; + status_t err = getEntry(grp, t, e, NULL, &entry); + if (err != NO_ERROR) { + return false; + } + + return grp->isDynamic; +} + static bool keyCompare(const ResTable_sparseTypeEntry& entry , uint16_t entryIdx) { return dtohs(entry.idx) < entryIdx; } @@ -6318,12 +6590,14 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, id = targetPackageId; } + bool isDynamic = false; if (id >= 256) { LOG_ALWAYS_FATAL("Package id out of range"); return NO_ERROR; } else if (id == 0 || (id == 0x7f && appAsLib) || isSystemAsset) { // This is a library or a system asset, so assign an ID id = mNextPackageId++; + isDynamic = true; } PackageGroup* group = NULL; @@ -6351,10 +6625,9 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, size_t idx = mPackageMap[id]; if (idx == 0) { idx = mPackageGroups.size() + 1; - char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])]; strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0])); - group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset); + group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset, isDynamic); if (group == NULL) { delete package; return (mError=NO_MEMORY); @@ -6551,8 +6824,16 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, } } else if (ctype == RES_TABLE_LIBRARY_TYPE) { + if (group->dynamicRefTable.entries().size() == 0) { - status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk); + const ResTable_lib_header* lib = (const ResTable_lib_header*) chunk; + status_t err = validate_chunk(&lib->header, sizeof(*lib), + endPos, "ResTable_lib_header"); + if (err != NO_ERROR) { + return (mError=err); + } + + err = group->dynamicRefTable.load(lib); if (err != NO_ERROR) { return (mError=err); } @@ -6592,6 +6873,13 @@ DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib) mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID; } +std::unique_ptr<DynamicRefTable> DynamicRefTable::clone() const { + std::unique_ptr<DynamicRefTable> clone = std::unique_ptr<DynamicRefTable>( + new DynamicRefTable(mAssignedPackageId, mAppAsLib)); + clone->addMappings(*this); + return clone; +} + status_t DynamicRefTable::load(const ResTable_lib_header* const header) { const uint32_t entryCount = dtohl(header->count); @@ -6632,7 +6920,7 @@ status_t DynamicRefTable::addMappings(const DynamicRefTable& other) { for (size_t i = 0; i < entryCount; i++) { ssize_t index = mEntries.indexOfKey(other.mEntries.keyAt(i)); if (index < 0) { - mEntries.add(other.mEntries.keyAt(i), other.mEntries[i]); + mEntries.add(String16(other.mEntries.keyAt(i)), other.mEntries[i]); } else { if (other.mEntries[i] != mEntries[index]) { return UNKNOWN_ERROR; @@ -6759,6 +7047,9 @@ status_t ResTable::createIdmap(const ResTable& overlay, return UNKNOWN_ERROR; } + // The number of resources overlaid that were not explicitly marked overlayable. + size_t forcedOverlayCount = 0u; + KeyedVector<uint8_t, IdmapTypeMap> map; // overlaid packages are assumed to contain only one package group @@ -6798,6 +7089,7 @@ status_t ResTable::createIdmap(const ResTable& overlay, continue; } + uint32_t typeSpecFlags = 0u; const String16 overlayType(resName.type, resName.typeLen); const String16 overlayName(resName.name, resName.nameLen); uint32_t overlayResID = overlay.identifierForName(overlayName.string(), @@ -6805,14 +7097,23 @@ status_t ResTable::createIdmap(const ResTable& overlay, overlayType.string(), overlayType.size(), overlayPackage.string(), - overlayPackage.size()); + overlayPackage.size(), + &typeSpecFlags); if (overlayResID == 0) { + // No such target resource was found. if (typeMap.entryMap.isEmpty()) { typeMap.entryOffset++; } continue; } + // Now that we know this is being overlaid, check if it can be, and emit a warning if + // it can't. + if ((dtohl(typeConfigs->typeSpecFlags[entryIndex]) & + ResTable_typeSpec::SPEC_OVERLAYABLE) == 0) { + forcedOverlayCount++; + } + if (typeMap.overlayTypeId == -1) { typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1; } @@ -6891,6 +7192,10 @@ status_t ResTable::createIdmap(const ResTable& overlay, typeData += entryCount * 2; } + if (forcedOverlayCount > 0) { + ALOGW("idmap: overlaid %zu resources not marked overlayable", forcedOverlayCount); + } + return NO_ERROR; } @@ -7078,222 +7383,238 @@ void ResTable::print(bool inclValues) const printf("\n"); } - int packageId = pg->id; - size_t pkgCount = pg->packages.size(); - for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) { - const Package* pkg = pg->packages[pkgIndex]; - // Use a package's real ID, since the ID may have been assigned - // if this package is a shared library. - packageId = pkg->package->id; - char16_t tmpName[sizeof(pkg->package->name)/sizeof(pkg->package->name[0])]; - strcpy16_dtoh(tmpName, pkg->package->name, sizeof(pkg->package->name)/sizeof(pkg->package->name[0])); - printf(" Package %d id=0x%02x name=%s\n", (int)pkgIndex, - pkg->package->id, String8(tmpName).string()); - } - - for (size_t typeIndex=0; typeIndex < pg->types.size(); typeIndex++) { - const TypeList& typeList = pg->types[typeIndex]; - if (typeList.isEmpty()) { - continue; - } - const Type* typeConfigs = typeList[0]; - const size_t NTC = typeConfigs->configs.size(); - printf(" type %d configCount=%d entryCount=%d\n", - (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); - if (typeConfigs->typeSpecFlags != NULL) { - for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) { - uint32_t resID = (0xff000000 & ((packageId)<<24)) - | (0x00ff0000 & ((typeIndex+1)<<16)) - | (0x0000ffff & (entryIndex)); - // Since we are creating resID without actually - // iterating over them, we have no idea which is a - // dynamic reference. We must check. - if (packageId == 0) { - pg->dynamicRefTable.lookupResourceId(&resID); - } - - resource_name resName; - if (this->getResourceName(resID, true, &resName)) { - String8 type8; - String8 name8; - if (resName.type8 != NULL) { - type8 = String8(resName.type8, resName.typeLen); - } else { - type8 = String8(resName.type, resName.typeLen); - } - if (resName.name8 != NULL) { - name8 = String8(resName.name8, resName.nameLen); - } else { - name8 = String8(resName.name, resName.nameLen); - } - printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", - resID, - CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string(), - dtohl(typeConfigs->typeSpecFlags[entryIndex])); - } else { - printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); - } - } - } - for (size_t configIndex=0; configIndex<NTC; configIndex++) { - const ResTable_type* type = typeConfigs->configs[configIndex]; - if ((((uint64_t)type)&0x3) != 0) { - printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); - continue; - } - - // Always copy the config, as fields get added and we need to - // set the defaults. - ResTable_config thisConfig; - thisConfig.copyFromDtoH(type->config); - - String8 configStr = thisConfig.toString(); - printf(" config %s", configStr.size() > 0 - ? configStr.string() : "(default)"); - if (type->flags != 0u) { - printf(" flags=0x%02x", type->flags); - if (type->flags & ResTable_type::FLAG_SPARSE) { - printf(" [sparse]"); - } - } - - printf(":\n"); + // Determine the number of resource splits for the resource types in this package. + // It needs to be done outside of the loop below so all of the information for a + // is displayed in a single block. Otherwise, a resource split's resource types + // would be interleaved with other splits. + size_t splitCount = 0; + for (size_t typeIndex = 0; typeIndex < pg->types.size(); typeIndex++) { + splitCount = max(splitCount, pg->types[typeIndex].size()); + } - size_t entryCount = dtohl(type->entryCount); - uint32_t entriesStart = dtohl(type->entriesStart); - if ((entriesStart&0x3) != 0) { - printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart); - continue; - } - uint32_t typeSize = dtohl(type->header.size); - if ((typeSize&0x3) != 0) { - printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize); + int packageId = pg->id; + for (size_t splitIndex = 0; splitIndex < splitCount; splitIndex++) { + size_t pkgCount = pg->packages.size(); + for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) { + const Package* pkg = pg->packages[pkgIndex]; + // Use a package's real ID, since the ID may have been assigned + // if this package is a shared library. + packageId = pkg->package->id; + char16_t tmpName[sizeof(pkg->package->name)/sizeof(pkg->package->name[0])]; + strcpy16_dtoh(tmpName, pkg->package->name, + sizeof(pkg->package->name)/sizeof(pkg->package->name[0])); + printf(" Package %d id=0x%02x name=%s\n", (int)pkgIndex, + pkg->package->id, String8(tmpName).string()); + } + + for (size_t typeIndex = 0; typeIndex < pg->types.size(); typeIndex++) { + const TypeList& typeList = pg->types[typeIndex]; + if (splitIndex >= typeList.size() || typeList.isEmpty()) { + // Only dump if the split exists and contains entries for this type continue; } - - const uint32_t* const eindex = (const uint32_t*) - (((const uint8_t*)type) + dtohs(type->header.headerSize)); - for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) { - size_t entryId; - uint32_t thisOffset; - if (type->flags & ResTable_type::FLAG_SPARSE) { - const ResTable_sparseTypeEntry* entry = - reinterpret_cast<const ResTable_sparseTypeEntry*>( - eindex + entryIndex); - entryId = dtohs(entry->idx); - // Offsets are encoded as divided by 4. - thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u; - } else { - entryId = entryIndex; - thisOffset = dtohl(eindex[entryIndex]); - if (thisOffset == ResTable_type::NO_ENTRY) { - continue; + const Type* typeConfigs = typeList[splitIndex]; + const size_t NTC = typeConfigs->configs.size(); + printf(" type %d configCount=%d entryCount=%d\n", + (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); + if (typeConfigs->typeSpecFlags != NULL) { + for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) { + uint32_t resID = (0xff000000 & ((packageId)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + // Since we are creating resID without actually + // iterating over them, we have no idea which is a + // dynamic reference. We must check. + if (packageId == 0) { + pg->dynamicRefTable.lookupResourceId(&resID); } - } - uint32_t resID = (0xff000000 & ((packageId)<<24)) - | (0x00ff0000 & ((typeIndex+1)<<16)) - | (0x0000ffff & (entryId)); - if (packageId == 0) { - pg->dynamicRefTable.lookupResourceId(&resID); - } - resource_name resName; - if (this->getResourceName(resID, true, &resName)) { - String8 type8; - String8 name8; - if (resName.type8 != NULL) { - type8 = String8(resName.type8, resName.typeLen); - } else { - type8 = String8(resName.type, resName.typeLen); - } - if (resName.name8 != NULL) { - name8 = String8(resName.name8, resName.nameLen); + resource_name resName; + if (this->getResourceName(resID, true, &resName)) { + String8 type8; + String8 name8; + if (resName.type8 != NULL) { + type8 = String8(resName.type8, resName.typeLen); + } else { + type8 = String8(resName.type, resName.typeLen); + } + if (resName.name8 != NULL) { + name8 = String8(resName.name8, resName.nameLen); + } else { + name8 = String8(resName.name, resName.nameLen); + } + printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", + resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + type8.string(), name8.string(), + dtohl(typeConfigs->typeSpecFlags[entryIndex])); } else { - name8 = String8(resName.name, resName.nameLen); + printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); } - printf(" resource 0x%08x %s:%s/%s: ", resID, - CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string()); - } else { - printf(" INVALID RESOURCE 0x%08x: ", resID); - } - if ((thisOffset&0x3) != 0) { - printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset); - continue; } - if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { - printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n", - entriesStart, thisOffset, typeSize); + } + for (size_t configIndex=0; configIndex<NTC; configIndex++) { + const ResTable_type* type = typeConfigs->configs[configIndex]; + if ((((uint64_t)type)&0x3) != 0) { + printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); continue; } - const ResTable_entry* ent = (const ResTable_entry*) - (((const uint8_t*)type) + entriesStart + thisOffset); - if (((entriesStart + thisOffset)&0x3) != 0) { - printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n", - (entriesStart + thisOffset)); - continue; + // Always copy the config, as fields get added and we need to + // set the defaults. + ResTable_config thisConfig; + thisConfig.copyFromDtoH(type->config); + + String8 configStr = thisConfig.toString(); + printf(" config %s", configStr.size() > 0 + ? configStr.string() : "(default)"); + if (type->flags != 0u) { + printf(" flags=0x%02x", type->flags); + if (type->flags & ResTable_type::FLAG_SPARSE) { + printf(" [sparse]"); + } } - uintptr_t esize = dtohs(ent->size); - if ((esize&0x3) != 0) { - printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize); + printf(":\n"); + + size_t entryCount = dtohl(type->entryCount); + uint32_t entriesStart = dtohl(type->entriesStart); + if ((entriesStart&0x3) != 0) { + printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", + entriesStart); continue; } - if ((thisOffset+esize) > typeSize) { - printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n", - entriesStart, thisOffset, (void *)esize, typeSize); + uint32_t typeSize = dtohl(type->header.size); + if ((typeSize&0x3) != 0) { + printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize); continue; } - const Res_value* valuePtr = NULL; - const ResTable_map_entry* bagPtr = NULL; - Res_value value; - if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { - printf("<bag>"); - bagPtr = (const ResTable_map_entry*)ent; - } else { - valuePtr = (const Res_value*) - (((const uint8_t*)ent) + esize); - value.copyFrom_dtoh(*valuePtr); - printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", - (int)value.dataType, (int)value.data, - (int)value.size, (int)value.res0); - } + const uint32_t* const eindex = (const uint32_t*) + (((const uint8_t*)type) + dtohs(type->header.headerSize)); + for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) { + size_t entryId; + uint32_t thisOffset; + if (type->flags & ResTable_type::FLAG_SPARSE) { + const ResTable_sparseTypeEntry* entry = + reinterpret_cast<const ResTable_sparseTypeEntry*>( + eindex + entryIndex); + entryId = dtohs(entry->idx); + // Offsets are encoded as divided by 4. + thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u; + } else { + entryId = entryIndex; + thisOffset = dtohl(eindex[entryIndex]); + if (thisOffset == ResTable_type::NO_ENTRY) { + continue; + } + } - if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { - printf(" (PUBLIC)"); - } - printf("\n"); - - if (inclValues) { - if (valuePtr != NULL) { - printf(" "); - print_value(typeConfigs->package, value); - } else if (bagPtr != NULL) { - const int N = dtohl(bagPtr->count); - const uint8_t* baseMapPtr = (const uint8_t*)ent; - size_t mapOffset = esize; - const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); - const uint32_t parent = dtohl(bagPtr->parent.ident); - uint32_t resolvedParent = parent; - if (Res_GETPACKAGE(resolvedParent) + 1 == 0) { - status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent); - if (err != NO_ERROR) { - resolvedParent = 0; - } + uint32_t resID = (0xff000000 & ((packageId)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryId)); + if (packageId == 0) { + pg->dynamicRefTable.lookupResourceId(&resID); + } + resource_name resName; + if (this->getResourceName(resID, true, &resName)) { + String8 type8; + String8 name8; + if (resName.type8 != NULL) { + type8 = String8(resName.type8, resName.typeLen); + } else { + type8 = String8(resName.type, resName.typeLen); } - printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", - parent, resolvedParent, N); - for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { - printf(" #%i (Key=0x%08x): ", - i, dtohl(mapPtr->name.ident)); - value.copyFrom_dtoh(mapPtr->value); + if (resName.name8 != NULL) { + name8 = String8(resName.name8, resName.nameLen); + } else { + name8 = String8(resName.name, resName.nameLen); + } + printf(" resource 0x%08x %s:%s/%s: ", resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + type8.string(), name8.string()); + } else { + printf(" INVALID RESOURCE 0x%08x: ", resID); + } + if ((thisOffset&0x3) != 0) { + printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset); + continue; + } + if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { + printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n", + entriesStart, thisOffset, typeSize); + continue; + } + + const ResTable_entry* ent = (const ResTable_entry*) + (((const uint8_t*)type) + entriesStart + thisOffset); + if (((entriesStart + thisOffset)&0x3) != 0) { + printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n", + (entriesStart + thisOffset)); + continue; + } + + uintptr_t esize = dtohs(ent->size); + if ((esize&0x3) != 0) { + printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize); + continue; + } + if ((thisOffset+esize) > typeSize) { + printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n", + entriesStart, thisOffset, (void *)esize, typeSize); + continue; + } + + const Res_value* valuePtr = NULL; + const ResTable_map_entry* bagPtr = NULL; + Res_value value; + if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { + printf("<bag>"); + bagPtr = (const ResTable_map_entry*)ent; + } else { + valuePtr = (const Res_value*) + (((const uint8_t*)ent) + esize); + value.copyFrom_dtoh(*valuePtr); + printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", + (int)value.dataType, (int)value.data, + (int)value.size, (int)value.res0); + } + + if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { + printf(" (PUBLIC)"); + } + printf("\n"); + + if (inclValues) { + if (valuePtr != NULL) { + printf(" "); print_value(typeConfigs->package, value); - const size_t size = dtohs(mapPtr->value.size); - mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value); - mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); + } else if (bagPtr != NULL) { + const int N = dtohl(bagPtr->count); + const uint8_t* baseMapPtr = (const uint8_t*)ent; + size_t mapOffset = esize; + const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); + const uint32_t parent = dtohl(bagPtr->parent.ident); + uint32_t resolvedParent = parent; + if (Res_GETPACKAGE(resolvedParent) + 1 == 0) { + status_t err = + pg->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + resolvedParent = 0; + } + } + printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", + parent, resolvedParent, N); + for (int i=0; + i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { + printf(" #%i (Key=0x%08x): ", + i, dtohl(mapPtr->name.ident)); + value.copyFrom_dtoh(mapPtr->value); + print_value(typeConfigs->package, value); + const size_t size = dtohs(mapPtr->value.size); + mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value); + mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); + } } } } diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index 1aa6cf6da28d..d63feb01ef83 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -26,6 +26,9 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin bool has_type_separator = false; const char* start = str.data(); const char* end = start + str.size(); + if (start[0] == '@') { + start++; + } const char* current = start; while (current != end) { if (out_type->size() == 0 && *current == '/') { diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp index 49fe8a261178..6e2ca60cc3d3 100644 --- a/libs/androidfw/ZipFileRO.cpp +++ b/libs/androidfw/ZipFileRO.cpp @@ -55,7 +55,9 @@ private: ZipFileRO::~ZipFileRO() { CloseArchive(mHandle); - free(mFileName); + if (mFileName != NULL) { + free(mFileName); + } } /* @@ -76,6 +78,20 @@ ZipFileRO::~ZipFileRO() { } +/* static */ ZipFileRO* ZipFileRO::openFd(int fd, const char* debugFileName, + bool assume_ownership) +{ + ZipArchiveHandle handle; + const int32_t error = OpenArchiveFd(fd, debugFileName, &handle, assume_ownership); + if (error) { + ALOGW("Error opening archive fd %d %s: %s", fd, debugFileName, ErrorCodeString(error)); + CloseArchive(handle); + return NULL; + } + + return new ZipFileRO(handle, strdup(debugFileName)); +} + ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const { _ZipEntryRO* data = new _ZipEntryRO; @@ -139,7 +155,8 @@ bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* su prefix ? &pe : NULL, suffix ? &se : NULL); if (error) { - ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error)); + ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>", + ErrorCodeString(error)); delete ze; return false; } @@ -154,7 +171,8 @@ ZipEntryRO ZipFileRO::nextEntry(void* cookie) int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name)); if (error) { if (error != -1) { - ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error)); + ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>", + ErrorCodeString(error)); } return NULL; } diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index b7e66fb68be5..69702e314442 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -21,7 +21,7 @@ #include <string> #include "android-base/macros.h" -#include "ziparchive/zip_archive.h" +#include "android-base/unique_fd.h" #include "androidfw/Asset.h" #include "androidfw/LoadedArsc.h" @@ -29,41 +29,73 @@ namespace android { +class LoadedIdmap; + // Holds an APK. class ApkAssets { public: + // Creates an ApkAssets. + // If `system` is true, the package is marked as a system package, and allows some functions to + // filter out this package when computing what configurations/resources are available. static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false); + + // Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library. + // If `system` is true, the package is marked as a system package, and allows some functions to + // filter out this package when computing what configurations/resources are available. static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const std::string& path, bool system = false); + // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay + // data. + // If `system` is true, the package is marked as a system package, and allows some functions to + // filter out this package when computing what configurations/resources are available. + static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path, + bool system = false); + + // Creates an ApkAssets from the given file descriptor, and takes ownership of the file + // descriptor. The `friendly_name` is some name that will be used to identify the source of + // this ApkAssets in log messages and other debug scenarios. + // If `system` is true, the package is marked as a system package, and allows some functions to + // filter out this package when computing what configurations/resources are available. + // If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library. + static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd, + const std::string& friendly_name, bool system, + bool force_shared_lib); + std::unique_ptr<Asset> Open(const std::string& path, Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const; bool ForEachFile(const std::string& path, const std::function<void(const StringPiece&, FileType)>& f) const; - inline const std::string& GetPath() const { return path_; } + inline const std::string& GetPath() const { + return path_; + } - inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); } + // This is never nullptr. + inline const LoadedArsc* GetLoadedArsc() const { + return loaded_arsc_.get(); + } private: DISALLOW_COPY_AND_ASSIGN(ApkAssets); - static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path, bool system, - bool load_as_shared_library); + static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path, + std::unique_ptr<Asset> idmap_asset, + std::unique_ptr<const LoadedIdmap> loaded_idmap, + bool system, bool load_as_shared_library); - ApkAssets() = default; + // Creates an Asset from any file on the file system. + static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path); - struct ZipArchivePtrCloser { - void operator()(::ZipArchiveHandle handle) { ::CloseArchive(handle); } - }; + ApkAssets(void* unmanaged_handle, const std::string& path); - using ZipArchivePtr = - std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>; + using ZipArchivePtr = std::unique_ptr<void, void(*)(void*)>; ZipArchivePtr zip_handle_; - std::string path_; + const std::string path_; std::unique_ptr<Asset> resources_asset_; + std::unique_ptr<Asset> idmap_asset_; std::unique_ptr<const LoadedArsc> loaded_arsc_; }; diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index 0441b9d789e2..08da7319de85 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -60,6 +60,7 @@ public: static const char* RESOURCES_FILENAME; static const char* IDMAP_BIN; static const char* OVERLAY_DIR; + static const char* PRODUCT_OVERLAY_DIR; /* * If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay * APKs in OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to @@ -92,6 +93,20 @@ public: bool addOverlayPath(const String8& path, int32_t* cookie); /* + * Add a new source for assets from an already open file descriptor. + * This does not give full AssetManager functionality for these assets, + * since the origin of the file is not known for purposes of sharing, + * overlay resolution, and other features. However it does allow you + * to do simple access to the contents of the given fd as an apk file. + * + * Returns "true" on success, "false" on failure. If 'cookie' is non-NULL, + * then on success, *cookie is set to the value corresponding to the + * newly-added asset source. + */ + bool addAssetFd(int fd, const String8& debugPathName, int32_t* cookie, + bool appAsLib=false, bool assume_ownership=true); + + /* * Convenience for adding the standard system assets. Uses the * ANDROID_ROOT environment variable to find them. */ @@ -195,24 +210,29 @@ public: uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize); private: + class SharedZip; + struct asset_path { - asset_path() : path(""), type(kFileTypeRegular), idmap(""), - isSystemOverlay(false), isSystemAsset(false) {} + asset_path() : path(""), rawFd(-1), type(kFileTypeRegular), idmap(""), + isSystemOverlay(false), isSystemAsset(false), assumeOwnership(false) {} String8 path; + int rawFd; FileType type; String8 idmap; bool isSystemOverlay; bool isSystemAsset; + bool assumeOwnership; + sp<SharedZip> zip; }; Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode, - const asset_path& path); + asset_path& path); String8 createPathNameLocked(const asset_path& path, const char* rootDir); String8 createZipSourceNameLocked(const String8& zipFileName, const String8& dirName, const String8& fileName); - ZipFileRO* getZipFileLocked(const asset_path& path); + ZipFileRO* getZipFileLocked(asset_path& path); Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode); Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile, const ZipEntryRO entry, AccessMode mode, const String8& entryName); @@ -228,7 +248,7 @@ private: const ResTable* getResTable(bool required = true) const; void setLocaleLocked(const char* locale); void updateResourceParamsLocked() const; - bool appendPathToResTable(const asset_path& ap, bool appAsLib=false) const; + bool appendPathToResTable(asset_path& ap, bool appAsLib=false) const; Asset* openIdmapLocked(const struct asset_path& ap) const; @@ -238,6 +258,7 @@ private: class SharedZip : public RefBase { public: static sp<SharedZip> get(const String8& path, bool createIfNotPresent = true); + static sp<SharedZip> create(int fd, const String8& path); ZipFileRO* getZip(); @@ -257,6 +278,7 @@ private: private: SharedZip(const String8& path, time_t modWhen); + SharedZip(int fd, const String8& path); SharedZip(); // <-- not implemented String8 mPath; @@ -290,6 +312,8 @@ private: */ ZipFileRO* getZip(const String8& path); + const sp<SharedZip> getSharedZip(const String8& path); + Asset* getZipResourceTableAsset(const String8& path); Asset* setZipResourceTableAsset(const String8& path, Asset* asset); diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index d2bc6ee45576..ad31f6940438 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -69,10 +69,11 @@ struct ResolvedBag { Entry entries[0]; }; +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 : public ::AAssetManager { +// AssetManager2 provides caching of resources retrieved via the underlying ApkAssets. +class AssetManager2 { public: struct ResourceName { const char* package = nullptr; @@ -97,24 +98,29 @@ class AssetManager2 : public ::AAssetManager { // new resource IDs. bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true); - inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; } + inline const std::vector<const ApkAssets*> GetApkAssets() const { + return apk_assets_; + } // Returns the string pool for the given asset cookie. - // Use the string pool returned here with a valid Res_value object of - // type Res_value::TYPE_STRING. + // Use the string pool returned here with a valid Res_value object of type Res_value::TYPE_STRING. const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const; // Returns the DynamicRefTable for the given package ID. + // This may be nullptr if the APK represented by `cookie` has no resource table. const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const; // Returns the DynamicRefTable for the ApkAssets represented by the cookie. + // This may be nullptr if the APK represented by `cookie` has no resource table. const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) 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); - inline const ResTable_config& GetConfiguration() const { return configuration_; } + inline const ResTable_config& GetConfiguration() const { + return configuration_; + } // Returns all configurations for which there are resources defined. This includes resource // configurations in all the ApkAssets set for this AssetManager. @@ -123,7 +129,7 @@ class AssetManager2 : public ::AAssetManager { // If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap' // will be excluded from the list. std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false, - bool exclude_mipmap = false); + bool exclude_mipmap = false) const; // Returns all the locales for which there are resources defined. This includes resource // locales in all the ApkAssets set for this AssetManager. @@ -132,24 +138,24 @@ class AssetManager2 : public ::AAssetManager { // If `merge_equivalent_languages` is set to true, resource locales will be canonicalized // and de-duped in the resulting list. std::set<std::string> GetResourceLocales(bool exclude_system = false, - bool merge_equivalent_languages = false); + bool merge_equivalent_languages = false) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found located // in the assets/ directory. // `mode` controls how the file is opened. // // NOTE: The loaded APKs are searched in reverse order. - std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode); + std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const; // Opens a file within the assets/ directory of the APK specified by `cookie`. // `mode` controls how the file is opened. std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Opens the directory specified by `dirname`. The result is an AssetDir that is the combination // of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded. // The entries are sorted by their ASCII name. - std::unique_ptr<AssetDir> OpenDir(const std::string& dirname); + std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found. // `mode` controls how the file is opened. @@ -157,24 +163,24 @@ class AssetManager2 : public ::AAssetManager { // // NOTE: The loaded APKs are searched in reverse order. std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie = nullptr); + ApkAssetsCookie* out_cookie = nullptr) const; // Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened. // This is typically used to open a specific AndroidManifest.xml, or a binary XML file // referenced by a resource lookup with GetResource(). std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Populates the `out_name` parameter with resource name information. // Utf8 strings are preferred, and only if they are unavailable are // the Utf16 variants populated. // Returns false if the resource was not found or the name was missing/corrupt. - bool GetResourceName(uint32_t resid, ResourceName* out_name); + bool GetResourceName(uint32_t resid, ResourceName* out_name) const; // Populates `out_flags` with the bitmask of configuration axis that this resource varies with. // See ResTable_config for the list of configuration axis. // Returns false if the resource was not found. - bool GetResourceFlags(uint32_t resid, uint32_t* out_flags); + bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const; // Finds the resource ID assigned to `resource_name`. // `resource_name` must be of the form '[package:][type/]entry'. @@ -182,7 +188,7 @@ class AssetManager2 : public ::AAssetManager { // If no type is specified in `resource_name`, then `fallback_type` is used as the type. // Returns 0x0 if no resource by that name was found. uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {}, - const std::string& fallback_package = {}); + const std::string& fallback_package = {}) const; // Retrieves the best matching resource with ID `resid`. The resource value is filled into // `out_value` and the configuration for the selected value is populated in `out_selected_config`. @@ -195,7 +201,7 @@ class AssetManager2 : public ::AAssetManager { // this function logs if the resource was a map/bag type before returning kInvalidCookie. ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags); + uint32_t* out_flags) const; // Resolves the resource reference in `in_out_value` if the data type is // Res_value::TYPE_REFERENCE. @@ -206,12 +212,12 @@ class AssetManager2 : public ::AAssetManager { // are OR'd together with `in_out_flags`. // `in_out_config` is populated with the configuration for which the resolved value was defined. // `out_last_reference` is populated with the last reference ID before resolving to an actual - // value. + // value. This is only initialized if the passed in `in_out_value` is a reference. // Returns the cookie of the APK the resolved resource was defined in, or kInvalidCookie if // it was not found. ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference); + uint32_t* out_last_reference) const; // Retrieves the best matching bag/map resource with ID `resid`. // This method will resolve all parent references for this bag and merge keys with the child. @@ -228,26 +234,35 @@ class AssetManager2 : public ::AAssetManager { // Creates a new Theme from this AssetManager. std::unique_ptr<Theme> NewTheme(); + template <typename Func> + void ForEachPackage(Func func) const { + for (const PackageGroup& package_group : package_groups_) { + func(package_group.packages_.front().loaded_package_->GetPackageName(), + package_group.dynamic_ref_table.mAssignedPackageId); + } + } + void DumpToLog() const; private: DISALLOW_COPY_AND_ASSIGN(AssetManager2); - // Finds the best entry for `resid` amongst all the ApkAssets. The entry can be a simple - // Res_value, or a complex map/bag type. + // Finds the best entry for `resid` from the set of ApkAssets. The entry can be a simple + // Res_value, or a complex map/bag type. If successful, it is available in `out_entry`. + // Returns kInvalidCookie on failure. Otherwise, the return value is the cookie associated with + // the ApkAssets in which the entry was found. // // `density_override` overrides the density of the current configuration when doing a search. // // When `stop_at_first_match` is true, the first match found is selected and the search // terminates. This is useful for methods that just look up the name of a resource and don't - // care about the value. In this case, the value of `out_flags` is incomplete and should not - // be used. + // care about the value. In this case, the value of `FindEntryResult::type_flags` is incomplete + // and should not be used. // - // `out_flags` stores the resulting bitmask of configuration axis with which the resource - // value varies. + // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly + // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds. ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match, - LoadedArscEntry* out_entry, ResTable_config* out_selected_config, - uint32_t* out_flags); + FindEntryResult* out_entry) const; // Assigns package IDs to all shared library ApkAssets. // Should be called whenever the ApkAssets are changed. @@ -257,13 +272,47 @@ class AssetManager2 : public ::AAssetManager { // bitmask `diff`. void InvalidateCaches(uint32_t diff); + // Triggers the re-construction of lists of types that match the set configuration. + // This should always be called when mutating the AssetManager's configuration or ApkAssets set. + void RebuildFilterList(); + + // AssetManager2::GetBag(resid) wraps this function to track which resource ids have already + // been seen while traversing bag parents. + const ResolvedBag* GetBag(uint32_t resid, std::vector<uint32_t>& child_resids); + // 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_; + // A collection of configurations and their associated ResTable_type that match the current + // AssetManager configuration. + struct FilteredConfigGroup { + std::vector<ResTable_config> configurations; + std::vector<const ResTable_type*> types; + }; + + // Represents an single package. + struct ConfiguredPackage { + // A pointer to the immutable, loaded package info. + const LoadedPackage* loaded_package_; + + // A mutable AssetManager-specific list of configurations that match the AssetManager's + // current configuration. This is used as an optimization to avoid checking every single + // candidate configuration when looking up resources. + ByteBucketArray<FilteredConfigGroup> filtered_configs_; + }; + + // Represents a logical package, which can be made up of many individual packages. Each package + // in a PackageGroup shares the same package name and package ID. struct PackageGroup { - std::vector<const LoadedPackage*> packages_; + // The set of packages that make-up this group. + std::vector<ConfiguredPackage> packages_; + + // The cookies associated with each package in the group. They share the same order as + // packages_. std::vector<ApkAssetsCookie> cookies_; + + // A library reference table that contains build-package ID to runtime-package ID mappings. DynamicRefTable dynamic_ref_table; }; @@ -290,6 +339,8 @@ class Theme { friend class AssetManager2; public: + ~Theme(); + // Applies the style identified by `resid` to this theme. This can be called // multiple times with different styles. By default, any theme attributes that // are already defined before this call are not overridden. If `force` is set @@ -304,69 +355,58 @@ class Theme { void Clear(); - inline const AssetManager2* GetAssetManager() const { return asset_manager_; } + inline const AssetManager2* GetAssetManager() const { + return asset_manager_; + } - inline AssetManager2* GetAssetManager() { return asset_manager_; } + inline AssetManager2* GetAssetManager() { + return asset_manager_; + } // Returns a bit mask of configuration changes that will impact this // theme (and thus require completely reloading it). - inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; } - - // Retrieve a value in the theme. If the theme defines this value, - // returns an asset cookie indicating which ApkAssets it came from - // and populates `out_value` with the value. If `out_flags` is non-null, - // populates it with a bitmask of the configuration axis the resource - // varies with. + inline uint32_t GetChangingConfigurations() const { + return type_spec_flags_; + } + + // Retrieve a value in the theme. If the theme defines this value, returns an asset cookie + // indicating which ApkAssets it came from and populates `out_value` with the value. + // `out_flags` is populated with a bitmask of the configuration axis with which the resource + // varies. // // If the attribute is not found, returns kInvalidCookie. // - // NOTE: This function does not do reference traversal. If you want - // to follow references to other resources to get the "real" value to - // use, you need to call ResolveReference() after this function. - ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value, - uint32_t* out_flags = nullptr) const; + // NOTE: This function does not do reference traversal. If you want to follow references to other + // resources to get the "real" value to use, you need to call ResolveReference() after this + // function. + ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value, uint32_t* out_flags) const; // This is like AssetManager2::ResolveReference(), but also takes // care of resolving attribute references to the theme. ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config = nullptr, uint32_t* in_out_type_spec_flags = nullptr, - uint32_t* out_last_ref = nullptr); + uint32_t* out_last_ref = nullptr) const; private: DISALLOW_COPY_AND_ASSIGN(Theme); // Called by AssetManager2. - explicit inline Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {} - - struct Entry { - ApkAssetsCookie cookie; - uint32_t type_spec_flags; - Res_value value; - }; - - struct Type { - // Use uint32_t for fewer cycles when loading from memory. - uint32_t entry_count; - uint32_t entry_capacity; - Entry entries[0]; - }; - - static constexpr const size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1; - static constexpr const size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1; - - struct Package { - // Each element of Type will be a dynamically sized object - // allocated to have the entries stored contiguously with the Type. - std::array<util::unique_cptr<Type>, kTypeCount> types; - }; + explicit Theme(AssetManager2* asset_manager); AssetManager2* asset_manager_; uint32_t type_spec_flags_ = 0u; + + // Defined in the cpp. + struct Package; + + constexpr static size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1; std::array<std::unique_ptr<Package>, kPackageCount> packages_; }; -inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; } +inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { + return bag->entries; +} inline const ResolvedBag::Entry* end(const ResolvedBag* bag) { return bag->entries + bag->entry_count; diff --git a/libs/androidfw/include/androidfw/AttributeFinder.h b/libs/androidfw/include/androidfw/AttributeFinder.h index f281921824e7..03fad4947dfe 100644 --- a/libs/androidfw/include/androidfw/AttributeFinder.h +++ b/libs/androidfw/include/androidfw/AttributeFinder.h @@ -58,6 +58,7 @@ class BackTrackingAttributeFinder { BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end); Iterator Find(uint32_t attr); + inline Iterator end(); private: void JumpToClosestAttribute(uint32_t package_id); @@ -201,6 +202,11 @@ Iterator BackTrackingAttributeFinder<Derived, Iterator>::Find(uint32_t attr) { return end_; } +template <typename Derived, typename Iterator> +Iterator BackTrackingAttributeFinder<Derived, Iterator>::end() { + return end_; +} + } // namespace android #endif // ANDROIDFW_ATTRIBUTE_FINDER_H diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h index 69b760414846..35ef98d8c704 100644 --- a/libs/androidfw/include/androidfw/AttributeResolution.h +++ b/libs/androidfw/include/androidfw/AttributeResolution.h @@ -17,7 +17,8 @@ #ifndef ANDROIDFW_ATTRIBUTERESOLUTION_H #define ANDROIDFW_ATTRIBUTERESOLUTION_H -#include <androidfw/ResourceTypes.h> +#include "androidfw/AssetManager2.h" +#include "androidfw/ResourceTypes.h" namespace android { @@ -42,19 +43,19 @@ enum { // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_resid, uint32_t* src_values, size_t src_values_length, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` is NOT optional and must NOT be nullptr. -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs, +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); } // namespace android diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h new file mode 100644 index 000000000000..fd02e6f63b74 --- /dev/null +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP_H_ +#define IDMAP_H_ + +#include <memory> +#include <string> +#include <unordered_map> + +#include "android-base/macros.h" + +#include "androidfw/StringPiece.h" + +namespace android { + +struct Idmap_header; +struct IdmapEntry_header; + +// Represents a loaded/parsed IDMAP for a Runtime Resource Overlay (RRO). +// An RRO and its target APK have different resource IDs assigned to their resources. Overlaying +// a resource is done by resource name. An IDMAP is a generated mapping between the resource IDs +// of the RRO and the target APK for each resource with the same name. +// A LoadedIdmap can be set alongside the overlay's LoadedArsc to allow the overlay ApkAssets to +// masquerade as the target ApkAssets resources. +class LoadedIdmap { + public: + // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed. + static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data); + + // Performs a lookup of the expected entry ID for the given IDMAP entry header. + // Returns true if the mapping exists and fills `output_entry_id` with the result. + static bool Lookup(const IdmapEntry_header* header, uint16_t input_entry_id, + uint16_t* output_entry_id); + + // Returns the package ID for which this overlay should apply. + uint8_t TargetPackageId() const; + + // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated. + inline const std::string& OverlayApkPath() const { + return overlay_apk_path_; + } + + // Returns the mapping of target entry ID to overlay entry ID for the given target type. + const IdmapEntry_header* GetEntryMapForType(uint8_t type_id) const; + + protected: + // Exposed as protected so that tests can subclass and mock this class out. + LoadedIdmap() = default; + + const Idmap_header* header_ = nullptr; + std::string overlay_apk_path_; + std::unordered_map<uint8_t, const IdmapEntry_header*> type_map_; + + private: + DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); + + explicit LoadedIdmap(const Idmap_header* header); +}; + +} // namespace android + +#endif // IDMAP_H_ diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index f30b158084eb..35ae5fcd9e7b 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -25,6 +25,7 @@ #include "androidfw/ByteBucketArray.h" #include "androidfw/Chunk.h" +#include "androidfw/Idmap.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" @@ -40,50 +41,94 @@ class DynamicPackageEntry { int package_id = 0; }; -struct LoadedArscEntry { - // A pointer to the resource table entry for this resource. - // If the size of the entry is > sizeof(ResTable_entry), it can be cast to - // a ResTable_map_entry and processed as a bag/map. - const ResTable_entry* entry = nullptr; +// TypeSpec is going to be immediately proceeded by +// an array of Type structs, all in the same block of memory. +struct TypeSpec { + // Pointer to the mmapped data where flags are kept. + // Flags denote whether the resource entry is public + // and under which configurations it varies. + const ResTable_typeSpec* type_spec; + + // Pointer to the mmapped data where the IDMAP mappings for this type + // exist. May be nullptr if no IDMAP exists. + const IdmapEntry_header* idmap_entries; + + // The number of types that follow this struct. + // There is a type for each configuration that entries are defined for. + size_t type_count; + + // Trick to easily access a variable number of Type structs + // proceeding this struct, and to ensure their alignment. + const ResTable_type* types[0]; + + inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const { + if (entry_index >= dtohl(type_spec->entryCount)) { + return 0u; + } + + const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1); + return flags[entry_index]; + } +}; - // The dynamic package ID map for the package from which this resource came from. - const DynamicRefTable* dynamic_ref_table = nullptr; +// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of +// ResTable_type pointers. +// TypeSpecPtr is a managed pointer that knows how to delete itself. +using TypeSpecPtr = util::unique_cptr<TypeSpec>; - // The string pool reference to the type's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef type_string_ref; +class LoadedPackage { + public: + static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk, + const LoadedIdmap* loaded_idmap, bool system, + bool load_as_shared_library); - // The string pool reference to the entry's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef entry_string_ref; -}; + ~LoadedPackage(); + + // Finds the entry with the specified type name and entry name. The names are in UTF-16 because + // the underlying ResStringPool API expects this. For now this is acceptable, but since + // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change. + // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible + // for patching the correct package ID to the resource ID. + uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const; -struct TypeSpec; -class LoadedArsc; + static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index); -class LoadedPackage { - friend class LoadedArsc; + static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index); - public: - bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - LoadedArscEntry* out_entry, ResTable_config* out_selected_config, - uint32_t* out_flags) const; + static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset); // Returns the string pool where type names are stored. - inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; } + inline const ResStringPool* GetTypeStringPool() const { + return &type_string_pool_; + } // Returns the string pool where the names of resource entries are stored. - inline const ResStringPool* GetKeyStringPool() const { return &key_string_pool_; } + inline const ResStringPool* GetKeyStringPool() const { + return &key_string_pool_; + } - inline const std::string& GetPackageName() const { return package_name_; } + inline const std::string& GetPackageName() const { + return package_name_; + } - inline int GetPackageId() const { return package_id_; } + inline int GetPackageId() const { + return package_id_; + } // Returns true if this package is dynamic (shared library) and needs to have an ID assigned. - inline bool IsDynamic() const { return dynamic_; } + inline bool IsDynamic() const { + return dynamic_; + } // Returns true if this package originates from a system provided resource. - inline bool IsSystem() const { return system_; } + inline bool IsSystem() const { + return system_; + } + + // Returns true if this package is from an overlay ApkAssets. + inline bool IsOverlay() const { + return overlay_; + } // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a // package could have been assigned a different package ID than what this LoadedPackage was @@ -101,19 +146,31 @@ class LoadedPackage { // before being inserted into the set. This may cause some equivalent locales to de-dupe. void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const; - // Finds the entry with the specified type name and entry name. The names are in UTF-16 because - // the underlying ResStringPool API expects this. For now this is acceptable, but since - // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change. - // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible - // for patching the correct package ID to the resource ID. - uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const; + // type_idx is TT - 1 from 0xPPTTEEEE. + inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const { + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + return type_specs_[type_index - type_id_offset_].get(); + } + + template <typename Func> + void ForEachTypeSpec(Func f) const { + for (size_t i = 0; i < type_specs_.size(); i++) { + const TypeSpecPtr& ptr = type_specs_[i]; + if (ptr != nullptr) { + uint8_t type_id = ptr->type_spec->id; + if (ptr->idmap_entries != nullptr) { + type_id = ptr->idmap_entries->target_type_id; + } + f(ptr.get(), type_id - 1); + } + } + } private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); - static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk); - - LoadedPackage() = default; + LoadedPackage(); ResStringPool type_string_pool_; ResStringPool key_string_pool_; @@ -122,8 +179,9 @@ class LoadedPackage { int type_id_offset_ = 0; bool dynamic_ = false; bool system_ = false; + bool overlay_ = false; - ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_; + ByteBucketArray<TypeSpecPtr> type_specs_; std::vector<DynamicPackageEntry> dynamic_package_map_; }; @@ -137,38 +195,39 @@ class LoadedArsc { // If `load_as_shared_library` is set to true, the application package (0x7f) is treated // as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an // ID. - static std::unique_ptr<const LoadedArsc> Load(const void* data, size_t len, bool system = false, + static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data, + const LoadedIdmap* loaded_idmap = nullptr, + bool system = false, bool load_as_shared_library = false); - ~LoadedArsc(); + // Create an empty LoadedArsc. This is used when an APK has no resources.arsc. + static std::unique_ptr<const LoadedArsc> CreateEmpty(); // Returns the string pool where all string resource values // (Res_value::dataType == Res_value::TYPE_STRING) are indexed. - inline const ResStringPool* GetStringPool() const { return &global_string_pool_; } - - // Finds the resource with ID `resid` with the best value for configuration `config`. - // The parameter `out_entry` will be filled with the resulting resource entry. - // The resource entry can be a simple entry (ResTable_entry) or a complex bag - // (ResTable_entry_map). - bool FindEntry(uint32_t resid, const ResTable_config& config, LoadedArscEntry* out_entry, - ResTable_config* selected_config, uint32_t* out_flags) const; + inline const ResStringPool* GetStringPool() const { + return &global_string_pool_; + } - // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist. - const LoadedPackage* GetPackageForId(uint32_t resid) const; - - // Returns true if this is a system provided resource. - inline bool IsSystem() const { return system_; } + // Gets a pointer to the package with the specified package ID, or nullptr if no such package + // exists. + const LoadedPackage* GetPackageById(uint8_t package_id) const; // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { return packages_; } + // Returns true if this is a system provided resource. + inline bool IsSystem() const { + return system_; + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedArsc); LoadedArsc() = default; - bool LoadTable(const Chunk& chunk, bool load_as_shared_library); + bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library); ResStringPool global_string_pool_; std::vector<std::unique_ptr<const LoadedPackage>> packages_; diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h new file mode 100644 index 000000000000..64924f433245 --- /dev/null +++ b/libs/androidfw/include/androidfw/MutexGuard.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROIDFW_MUTEXGUARD_H +#define ANDROIDFW_MUTEXGUARD_H + +#include <mutex> +#include <type_traits> + +#include "android-base/macros.h" + +namespace android { + +template <typename T> +class ScopedLock; + +// Owns the guarded object and protects access to it via a mutex. +// The guarded object is inaccessible via this class. +// The mutex is locked and the object accessed via the ScopedLock<T> class. +// +// NOTE: The template parameter T should not be a raw pointer, since ownership +// is ambiguous and error-prone. Instead use an std::unique_ptr<>. +// +// Example use: +// +// Guarded<std::string> shared_string("hello"); +// { +// ScopedLock<std::string> locked_string(shared_string); +// *locked_string += " world"; +// } +// +template <typename T> +class Guarded { + static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer"); + + public: + explicit Guarded() : guarded_() { + } + + template <typename U = T> + explicit Guarded(const T& guarded, + typename std::enable_if<std::is_copy_constructible<U>::value>::type = void()) + : guarded_(guarded) { + } + + template <typename U = T> + explicit Guarded(T&& guarded, + typename std::enable_if<std::is_move_constructible<U>::value>::type = void()) + : guarded_(std::move(guarded)) { + } + + private: + friend class ScopedLock<T>; + + DISALLOW_COPY_AND_ASSIGN(Guarded); + + std::mutex lock_; + T guarded_; +}; + +template <typename T> +class ScopedLock { + public: + explicit ScopedLock(Guarded<T>& guarded) : lock_(guarded.lock_), guarded_(guarded.guarded_) { + } + + T& operator*() { + return guarded_; + } + + T* operator->() { + return &guarded_; + } + + T* get() { + return &guarded_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedLock); + + std::lock_guard<std::mutex> lock_; + T& guarded_; +}; + +} // namespace android + +#endif // ANDROIDFW_MUTEXGUARD_H diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 66c66c251d9b..a02851502c9b 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -38,6 +38,9 @@ namespace android { +constexpr const static uint32_t kIdmapMagic = 0x504D4449u; +constexpr const static uint32_t kIdmapCurrentVersion = 0x00000001u; + /** * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of * casting on raw data and expect char16_t to be exactly 16 bits. @@ -535,6 +538,9 @@ private: uint32_t mStringPoolSize; // number of uint16_t const uint32_t* mStyles; uint32_t mStylePoolSize; // number of uint32_t + + const char* stringDecodeAt(size_t idx, const uint8_t* str, const size_t encLen, + size_t* outLen) const; }; /** @@ -543,15 +549,15 @@ private: */ class StringPoolRef { public: - StringPoolRef(); - StringPoolRef(const ResStringPool* pool, uint32_t index); + StringPoolRef() = default; + StringPoolRef(const ResStringPool* pool, uint32_t index); - const char* string8(size_t* outLen) const; - const char16_t* string16(size_t* outLen) const; + const char* string8(size_t* outLen) const; + const char16_t* string16(size_t* outLen) const; private: - const ResStringPool* mPool; - uint32_t mIndex; + const ResStringPool* mPool = nullptr; + uint32_t mIndex = 0u; }; /** ******************************************************************** @@ -793,6 +799,11 @@ class DynamicRefTable; class ResXMLTree : public ResXMLParser { public: + /** + * Creates a ResXMLTree with the specified DynamicRefTable for run-time package id translation. + * The tree stores a clone of the specified DynamicRefTable, so any changes to the original + * DynamicRefTable will not affect this tree after instantiation. + **/ ResXMLTree(const DynamicRefTable* dynamicRefTable); ResXMLTree(); ~ResXMLTree(); @@ -808,7 +819,7 @@ private: status_t validateNode(const ResXMLTree_node* node) const; - const DynamicRefTable* const mDynamicRefTable; + std::unique_ptr<const DynamicRefTable> mDynamicRefTable; status_t mError; void* mOwnedData; @@ -891,9 +902,10 @@ struct ResTable_package // - a 8 char variant code prefixed by a 'v' // // each separated by a single char separator, which sums up to a total of 24 -// chars, (25 include the string terminator) rounded up to 28 to be 4 byte -// aligned. -#define RESTABLE_MAX_LOCALE_LEN 28 +// chars, (25 include the string terminator). Numbering system specificator, +// if present, can add up to 14 bytes (-u-nu-xxxxxxxx), giving 39 bytes, +// or 40 bytes to make it 4 bytes aligned. +#define RESTABLE_MAX_LOCALE_LEN 40 /** @@ -1179,6 +1191,10 @@ struct ResTable_config // tried but could not compute a script. bool localeScriptWasComputed; + // The value of BCP 47 Unicode extension for key 'nu' (numbering system). + // Varies in length from 3 to 8 chars. Zero-filled value. + char localeNumberingSystem[8]; + void copyFromDeviceNoSwap(const ResTable_config& o); void copyFromDtoH(const ResTable_config& o); @@ -1256,9 +1272,9 @@ struct ResTable_config // variants, it will be a modified bcp47 tag: b+en+Latn+US. void appendDirLocale(String8& str) const; - // Sets the values of language, region, script and variant to the - // well formed BCP-47 locale contained in |in|. The input locale is - // assumed to be valid and no validation is performed. + // Sets the values of language, region, script, variant and numbering + // system to the well formed BCP 47 locale contained in |in|. + // The input locale is assumed to be valid and no validation is performed. void setBcp47Locale(const char* in); inline void clearLocale() { @@ -1266,6 +1282,7 @@ struct ResTable_config localeScriptWasComputed = false; memset(localeScript, 0, sizeof(localeScript)); memset(localeVariant, 0, sizeof(localeVariant)); + memset(localeNumberingSystem, 0, sizeof(localeNumberingSystem)); } inline void computeScript() { @@ -1295,6 +1312,9 @@ struct ResTable_config // and 0 if they're equally specific. int isLocaleMoreSpecificThan(const ResTable_config &o) const; + // Returns an integer representng the imporance score of the configuration locale. + int getImportanceScoreOfLocale() const; + // Return true if 'this' is a better locale match than 'o' for the // 'requested' configuration. Similar to isBetterThan(), this assumes that // match() has already been used to remove any configurations that don't @@ -1331,9 +1351,13 @@ struct ResTable_typeSpec // Number of uint32_t entry configuration masks that follow. uint32_t entryCount; - enum { + enum : uint32_t { // Additional flag indicating an entry is public. - SPEC_PUBLIC = 0x40000000 + SPEC_PUBLIC = 0x40000000u, + + // Additional flag indicating an entry is overlayable at runtime. + // Added in Android-P. + SPEC_OVERLAYABLE = 0x80000000u, }; }; @@ -1583,6 +1607,30 @@ struct ResTable_lib_entry uint16_t packageName[128]; }; +struct alignas(uint32_t) Idmap_header { + // Always 0x504D4449 ('IDMP') + uint32_t magic; + + uint32_t version; + + uint32_t target_crc32; + uint32_t overlay_crc32; + + uint8_t target_path[256]; + uint8_t overlay_path[256]; + + uint16_t target_package_id; + uint16_t type_count; +} __attribute__((packed)); + +struct alignas(uint32_t) IdmapEntry_header { + uint16_t target_type_id; + uint16_t overlay_type_id; + uint16_t entry_count; + uint16_t entry_id_offset; + uint32_t entries[0]; +} __attribute__((packed)); + class AssetManager2; /** @@ -1612,6 +1660,9 @@ public: void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId); + // Creates a new clone of the reference table + std::unique_ptr<DynamicRefTable> clone() const; + // Performs the actual conversion of build-time resource ID to run-time // resource ID. status_t lookupResourceId(uint32_t* resId) const; @@ -1673,6 +1724,18 @@ public: bool getResourceFlags(uint32_t resID, uint32_t* outFlags) const; /** + * Returns whether or not the package for the given resource has been dynamically assigned. + * If the resource can't be found, returns 'false'. + */ + bool isResourceDynamic(uint32_t resID) const; + + /** + * Returns whether or not the given package has been dynamically assigned. + * If the package can't be found, returns 'false'. + */ + bool isPackageDynamic(uint8_t packageID) const; + + /** * Retrieve the value of a resource. If the resource is found, returns a * value >= 0 indicating the table it is in (for use with * getTableStringBlock() and getTableCookie()) and fills in 'outValue'. If @@ -1981,6 +2044,7 @@ private: bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset=false); ssize_t getResourcePackageIndex(uint32_t resID) const; + ssize_t getResourcePackageIndexFromPackage(uint8_t packageID) const; status_t getEntry( const PackageGroup* packageGroup, int typeIndex, int entryIndex, diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index 6bf7c2438797..d94779bf5225 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -28,7 +28,7 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin StringPiece* out_entry); inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) { - return resid | (static_cast<uint32_t>(package_id) << 24); + return (resid & 0x00ffffffu) | (static_cast<uint32_t>(package_id) << 24); } inline uint8_t get_package_id(uint32_t resid) { @@ -40,7 +40,9 @@ inline uint8_t get_type_id(uint32_t resid) { return static_cast<uint8_t>((resid >> 16) & 0x000000ffu); } -inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); } +inline uint16_t get_entry_id(uint32_t resid) { + return static_cast<uint16_t>(resid & 0x0000ffffu); +} inline bool is_internal_resid(uint32_t resid) { return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0; diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h index 768034287afa..03154d04def1 100644 --- a/libs/androidfw/include/androidfw/ZipFileRO.h +++ b/libs/androidfw/include/androidfw/ZipFileRO.h @@ -80,6 +80,12 @@ public: static ZipFileRO* open(const char* zipFileName); /* + * Open an archive from an already open file descriptor. + */ + static ZipFileRO* openFd(int fd, const char* debugFileName, + bool assume_ownership = true); + + /* * Find an entry, by name. Returns the entry identifier, or NULL if * not found. */ diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk deleted file mode 100644 index 921fd147aa80..000000000000 --- a/libs/androidfw/tests/Android.mk +++ /dev/null @@ -1,126 +0,0 @@ -# -# Copyright (C) 2014 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# ========================================================== -# Setup some common variables for the different build -# targets here. -# ========================================================== -LOCAL_PATH:= $(call my-dir) - -testFiles := \ - ApkAssets_test.cpp \ - AppAsLib_test.cpp \ - Asset_test.cpp \ - AssetManager2_test.cpp \ - AttributeFinder_test.cpp \ - AttributeResolution_test.cpp \ - ByteBucketArray_test.cpp \ - Config_test.cpp \ - ConfigLocale_test.cpp \ - Idmap_test.cpp \ - LoadedArsc_test.cpp \ - ResourceUtils_test.cpp \ - ResTable_test.cpp \ - Split_test.cpp \ - StringPiece_test.cpp \ - TestHelpers.cpp \ - TestMain.cpp \ - Theme_test.cpp \ - TypeWrappers_test.cpp \ - ZipUtils_test.cpp - -benchmarkFiles := \ - AssetManager2_bench.cpp \ - BenchMain.cpp \ - BenchmarkHelpers.cpp \ - SparseEntry_bench.cpp \ - TestHelpers.cpp \ - Theme_bench.cpp - -androidfw_test_cflags := \ - -Wall \ - -Werror \ - -Wunused \ - -Wunreachable-code \ - -Wno-missing-field-initializers - -# gtest is broken. -androidfw_test_cflags += -Wno-unnamed-type-template-args - -# ========================================================== -# Build the host tests: libandroidfw_tests -# ========================================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := libandroidfw_tests -LOCAL_CFLAGS := $(androidfw_test_cflags) -LOCAL_SRC_FILES := $(testFiles) -LOCAL_STATIC_LIBRARIES := \ - libandroidfw \ - libbase \ - libutils \ - libcutils \ - liblog \ - libz \ - libziparchive -LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data - -include $(BUILD_HOST_NATIVE_TEST) - -# ========================================================== -# Build the device tests: libandroidfw_tests -# ========================================================== -ifneq ($(SDK_ONLY),true) -include $(CLEAR_VARS) - -LOCAL_MODULE := libandroidfw_tests -LOCAL_CFLAGS := $(androidfw_test_cflags) -LOCAL_SRC_FILES := $(testFiles) \ - BackupData_test.cpp \ - ObbFile_test.cpp \ - -LOCAL_SHARED_LIBRARIES := \ - libandroidfw \ - libbase \ - libcutils \ - libutils \ - libui \ - libziparchive -LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data - -include $(BUILD_NATIVE_TEST) - -# ========================================================== -# Build the device benchmarks: libandroidfw_benchmarks -# ========================================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := libandroidfw_benchmarks -LOCAL_CFLAGS := $(androidfw_test_cflags) -LOCAL_SRC_FILES := $(benchmarkFiles) -LOCAL_STATIC_LIBRARIES := \ - libgoogle-benchmark -LOCAL_SHARED_LIBRARIES := \ - libandroidfw \ - libbase \ - libcutils \ - libutils \ - libziparchive -LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data - -include $(BUILD_NATIVE_TEST) -endif # Not SDK_ONLY - diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index c85b0b98ca5e..e2b9f0040989 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -17,70 +17,122 @@ #include "androidfw/ApkAssets.h" #include "android-base/file.h" +#include "android-base/test_utils.h" #include "android-base/unique_fd.h" +#include "androidfw/Util.h" #include "TestHelpers.h" #include "data/basic/R.h" -using com::android::basic::R; +using ::android::base::unique_fd; +using ::com::android::basic::R; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; namespace android { TEST(ApkAssetsTest, LoadApk) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); - EXPECT_NE(nullptr, loaded_apk->GetLoadedArsc()); + ASSERT_THAT(loaded_apk, NotNull()); - std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); - ASSERT_NE(nullptr, asset); + const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); +} + +TEST(ApkAssetsTest, LoadApkFromFd) { + const std::string path = GetTestDataPath() + "/basic/basic.apk"; + unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY)); + ASSERT_THAT(fd.get(), Ge(0)); + + std::unique_ptr<const ApkAssets> loaded_apk = + ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/); + ASSERT_THAT(loaded_apk, NotNull()); + + const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); } TEST(ApkAssetsTest, LoadApkAsSharedLibrary) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); + const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic()); loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic()); } +TEST(ApkAssetsTest, LoadApkWithIdmap) { + std::string contents; + ResTable target_table; + const std::string target_path = GetTestDataPath() + "/basic/basic.apk"; + ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents)); + ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); + + ResTable overlay_table; + const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk"; + ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents)); + ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); + + util::unique_cptr<void> idmap_data; + void* temp_data; + size_t idmap_len; + + ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(), + overlay_path.c_str(), &temp_data, &idmap_len), + Eq(NO_ERROR)); + idmap_data.reset(temp_data); + + TemporaryFile tf; + ASSERT_TRUE(base::WriteFully(tf.fd, idmap_data.get(), idmap_len)); + close(tf.fd); + + // Open something so that the destructor of TemporaryFile closes a valid fd. + tf.fd = open("/dev/null", O_WRONLY); + + ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull()); +} + TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } } TEST(ApkAssetsTest, OpenUncompressedAssetFd) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(asset, NotNull()); off64_t start, length; - base::unique_fd fd(asset->openFileDescriptor(&start, &length)); - EXPECT_GE(fd.get(), 0); + unique_fd fd(asset->openFileDescriptor(&start, &length)); + ASSERT_THAT(fd.get(), Ge(0)); lseek64(fd.get(), start, SEEK_SET); @@ -88,7 +140,7 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) { buffer.resize(length); ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length)); - EXPECT_EQ("This should be uncompressed.\n\n", buffer); + EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n")); } } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 67de741b1b66..437e14772964 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -23,7 +23,6 @@ #include "androidfw/ResourceTypes.h" #include "BenchmarkHelpers.h" -#include "TestHelpers.h" #include "data/basic/R.h" #include "data/libclient/R.h" #include "data/styles/R.h" @@ -82,48 +81,18 @@ static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld); -static void GetResourceBenchmark(const std::vector<std::string>& paths, - const ResTable_config* config, uint32_t resid, - benchmark::State& state) { - std::vector<std::unique_ptr<const ApkAssets>> apk_assets; - std::vector<const ApkAssets*> apk_assets_ptrs; - for (const std::string& path : paths) { - std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path); - if (apk == nullptr) { - state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str()); - return; - } - apk_assets_ptrs.push_back(apk.get()); - apk_assets.push_back(std::move(apk)); - } - - AssetManager2 assetmanager; - assetmanager.SetApkAssets(apk_assets_ptrs); - if (config != nullptr) { - assetmanager.SetConfiguration(*config); - } - - Res_value value; - ResTable_config selected_config; - uint32_t flags; - - while (state.KeepRunning()) { - assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value, - &selected_config, &flags); - } -} - -static void BM_AssetManagerGetResource(benchmark::State& state) { - GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResource(benchmark::State& state, uint32_t resid) { + GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, state); } -BENCHMARK(BM_AssetManagerGetResource); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, deep_ref, basic::R::integer::deep_ref); -static void BM_AssetManagerGetResourceOld(benchmark::State& state) { - GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResourceOld(benchmark::State& state, uint32_t resid) { + GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, + state); } -BENCHMARK(BM_AssetManagerGetResourceOld); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, deep_ref, basic::R::integer::deep_ref); static void BM_AssetManagerGetLibraryResource(benchmark::State& state) { GetResourceBenchmark( @@ -228,7 +197,7 @@ BENCHMARK(BM_AssetManagerGetResourceLocales); static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { AssetManager assets; if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, - false /*isSystemAssets*/)) { + true /*isSystemAssets*/)) { state.SkipWithError("Failed to load assets"); return; } @@ -243,4 +212,44 @@ static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerGetResourceLocalesOld); +static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath); + if (apk == nullptr) { + state.SkipWithError("Failed to load assets"); + return; + } + + AssetManager2 assets; + assets.SetApkAssets({apk.get()}); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.SetConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFramework); + +static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) { + AssetManager assets; + if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, + true /*isSystemAssets*/)) { + state.SkipWithError("Failed to load assets"); + return; + } + + const ResTable& table = assets.getResources(true); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.setConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFrameworkOld); + } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index fcae53b322b3..f1cc569f7d4e 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -36,6 +36,10 @@ namespace lib_one = com::android::lib_one; namespace lib_two = com::android::lib_two; namespace libclient = com::android::libclient; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::StrEq; + namespace android { class AssetManager2Test : public ::testing::Test { @@ -59,11 +63,14 @@ class AssetManager2Test : public ::testing::Test { libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk"); ASSERT_NE(nullptr, libclient_assets_); - appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); + appaslib_assets_ = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); ASSERT_NE(nullptr, appaslib_assets_); system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/); ASSERT_NE(nullptr, system_assets_); + + app_assets_ = ApkAssets::Load(GetTestDataPath() + "/app/app.apk"); + ASSERT_THAT(app_assets_, NotNull()); } protected: @@ -75,6 +82,7 @@ class AssetManager2Test : public ::testing::Test { std::unique_ptr<const ApkAssets> libclient_assets_; std::unique_ptr<const ApkAssets> appaslib_assets_; std::unique_ptr<const ApkAssets> system_assets_; + std::unique_ptr<const ApkAssets> app_assets_; }; TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) { @@ -233,6 +241,25 @@ TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) { assetmanager.SetApkAssets( {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + const ResolvedBag* bag = assetmanager.GetBag(fix_package_id(lib_one::R::style::Theme, 0x03)); + ASSERT_NE(nullptr, bag); + ASSERT_GE(bag->entry_count, 2u); + + // First two attributes come from lib_one. + EXPECT_EQ(1, bag->entries[0].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[0].key)); + EXPECT_EQ(1, bag->entries[1].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[1].key)); +} + +TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) { + AssetManager2 assetmanager; + + // libclient is built with lib_one and then lib_two in order. + // Reverse the order to test that proper package ID re-assignment is happening. + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::Theme); ASSERT_NE(nullptr, bag); ASSERT_GE(bag->entry_count, 2u); @@ -302,6 +329,17 @@ TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) { EXPECT_EQ(0, bag_two->entries[5].cookie); } +TEST_F(AssetManager2Test, MergeStylesCircularDependency) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({style_assets_.get()}); + + // GetBag should stop traversing the parents of styles when a circular + // dependency is detected + const ResolvedBag* bag_one = assetmanager.GetBag(app::R::style::StyleFour); + ASSERT_NE(nullptr, bag_one); + ASSERT_EQ(3u, bag_one->entry_count); +} + TEST_F(AssetManager2Test, ResolveReferenceToResource) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); @@ -317,7 +355,7 @@ TEST_F(AssetManager2Test, ResolveReferenceToResource) { EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); EXPECT_EQ(basic::R::integer::ref2, value.data); - uint32_t last_ref; + uint32_t last_ref = 0u; cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref); ASSERT_NE(kInvalidCookie, cookie); EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType); @@ -340,7 +378,7 @@ TEST_F(AssetManager2Test, ResolveReferenceToBag) { EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); EXPECT_EQ(basic::R::array::integerArray1, value.data); - uint32_t last_ref; + uint32_t last_ref = 0u; cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref); ASSERT_NE(kInvalidCookie, cookie); EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); @@ -348,6 +386,57 @@ TEST_F(AssetManager2Test, ResolveReferenceToBag) { EXPECT_EQ(basic::R::array::integerArray1, last_ref); } +TEST_F(AssetManager2Test, ResolveDeepIdReference) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({basic_assets_.get()}); + + // Set up the resource ids + const uint32_t high_ref = assetmanager + .GetResourceId("@id/high_ref", "values", "com.android.basic"); + ASSERT_NE(high_ref, 0u); + const uint32_t middle_ref = assetmanager + .GetResourceId("@id/middle_ref", "values", "com.android.basic"); + ASSERT_NE(middle_ref, 0u); + const uint32_t low_ref = assetmanager + .GetResourceId("@id/low_ref", "values", "com.android.basic"); + ASSERT_NE(low_ref, 0u); + + // Retrieve the most shallow resource + Res_value value; + ResTable_config config; + uint32_t flags; + ApkAssetsCookie cookie = assetmanager.GetResource(high_ref, false /*may_be_bag*/, + 0 /*density_override*/, + &value, &config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType); + EXPECT_EQ(middle_ref, value.data); + + // Check that resolving the reference resolves to the deepest id + uint32_t last_ref = high_ref; + assetmanager.ResolveReference(cookie, &value, &config, &flags, &last_ref); + EXPECT_EQ(last_ref, low_ref); +} + +TEST_F(AssetManager2Test, KeepLastReferenceIdUnmodifiedIfNoReferenceIsResolved) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({basic_assets_.get()}); + + ResTable_config selected_config; + memset(&selected_config, 0, sizeof(selected_config)); + + uint32_t flags = 0u; + + // Create some kind of Res_value that is NOT a reference. + Res_value value; + value.dataType = Res_value::TYPE_STRING; + value.data = 0; + + uint32_t last_ref = basic::R::string::test1; + EXPECT_EQ(1, assetmanager.ResolveReference(1, &value, &selected_config, &flags, &last_ref)); + EXPECT_EQ(basic::R::string::test1, last_ref); +} + static bool IsConfigurationPresent(const std::set<ResTable_config>& configurations, const ResTable_config& configuration) { return configurations.count(configuration) > 0; @@ -427,8 +516,68 @@ TEST_F(AssetManager2Test, GetResourceId) { assetmanager.GetResourceId("main", "layout", "com.android.basic")); } -TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {} +TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({system_assets_.get()}); + + std::unique_ptr<Asset> asset = assetmanager.Open("file.txt", Asset::ACCESS_BUFFER); + ASSERT_THAT(asset, NotNull()); + + const char* data = reinterpret_cast<const char*>(asset->getBuffer(false /*wordAligned*/)); + ASSERT_THAT(data, NotNull()); + EXPECT_THAT(std::string(data, asset->getLength()), StrEq("file\n")); +} + +TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({system_assets_.get(), app_assets_.get()}); + + std::unique_ptr<Asset> asset = assetmanager.Open("file.txt", Asset::ACCESS_BUFFER); + ASSERT_THAT(asset, NotNull()); + + const char* data = reinterpret_cast<const char*>(asset->getBuffer(false /*wordAligned*/)); + ASSERT_THAT(data, NotNull()); + EXPECT_THAT(std::string(data, asset->getLength()), StrEq("app override file\n")); +} + +TEST_F(AssetManager2Test, OpenDir) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({system_assets_.get()}); + + std::unique_ptr<AssetDir> asset_dir = assetmanager.OpenDir(""); + ASSERT_THAT(asset_dir, NotNull()); + ASSERT_THAT(asset_dir->getFileCount(), Eq(2u)); + + EXPECT_THAT(asset_dir->getFileName(0), Eq(String8("file.txt"))); + EXPECT_THAT(asset_dir->getFileType(0), Eq(FileType::kFileTypeRegular)); + + EXPECT_THAT(asset_dir->getFileName(1), Eq(String8("subdir"))); + EXPECT_THAT(asset_dir->getFileType(1), Eq(FileType::kFileTypeDirectory)); + + asset_dir = assetmanager.OpenDir("subdir"); + ASSERT_THAT(asset_dir, NotNull()); + ASSERT_THAT(asset_dir->getFileCount(), Eq(1u)); -TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {} + EXPECT_THAT(asset_dir->getFileName(0), Eq(String8("subdir_file.txt"))); + EXPECT_THAT(asset_dir->getFileType(0), Eq(FileType::kFileTypeRegular)); +} + +TEST_F(AssetManager2Test, OpenDirFromManyApks) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({system_assets_.get(), app_assets_.get()}); + + std::unique_ptr<AssetDir> asset_dir = assetmanager.OpenDir(""); + ASSERT_THAT(asset_dir, NotNull()); + ASSERT_THAT(asset_dir->getFileCount(), Eq(3u)); + + EXPECT_THAT(asset_dir->getFileName(0), Eq(String8("app_file.txt"))); + EXPECT_THAT(asset_dir->getFileType(0), Eq(FileType::kFileTypeRegular)); + + EXPECT_THAT(asset_dir->getFileName(1), Eq(String8("file.txt"))); + EXPECT_THAT(asset_dir->getFileType(1), Eq(FileType::kFileTypeRegular)); + + EXPECT_THAT(asset_dir->getFileName(2), Eq(String8("subdir"))); + EXPECT_THAT(asset_dir->getFileType(2), Eq(FileType::kFileTypeDirectory)); +} } // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp new file mode 100644 index 000000000000..fa300c50218a --- /dev/null +++ b/libs/androidfw/tests/AttributeResolution_bench.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "benchmark/benchmark.h" + +//#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/AttributeResolution.h" +#include "androidfw/ResourceTypes.h" + +#include "BenchmarkHelpers.h" +#include "data/basic/R.h" +#include "data/styles/R.h" + +namespace app = com::android::app; +namespace basic = com::android::basic; + +namespace android { + +constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk"; +constexpr const static uint32_t Theme_Material_Light = 0x01030237u; + +static void BM_ApplyStyle(benchmark::State& state) { + std::unique_ptr<const ApkAssets> styles_apk = + ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + if (styles_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({styles_apk.get()}); + + std::unique_ptr<Asset> asset = + assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(app::R::style::StyleTwo); + + std::array<uint32_t, 6> attrs{{app::R::attr::attr_one, app::R::attr::attr_two, + app::R::attr::attr_three, app::R::attr::attr_four, + app::R::attr::attr_five, app::R::attr::attr_empty}}; + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), + attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyle); + +static void BM_ApplyStyleFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath); + if (framework_apk == nullptr) { + state.SkipWithError("failed to load framework assets"); + return; + } + + std::unique_ptr<const ApkAssets> basic_apk = + ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); + if (basic_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()}); + + ResTable_config device_config; + memset(&device_config, 0, sizeof(device_config)); + device_config.language[0] = 'e'; + device_config.language[1] = 'n'; + device_config.country[0] = 'U'; + device_config.country[1] = 'S'; + device_config.orientation = ResTable_config::ORIENTATION_PORT; + device_config.smallestScreenWidthDp = 700; + device_config.screenWidthDp = 700; + device_config.screenHeightDp = 1024; + device_config.sdkVersion = 27; + + Res_value value; + ResTable_config config; + uint32_t flags = 0u; + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::layout::layoutt, false /*may_be_bag*/, + 0u /*density_override*/, &value, &config, &flags); + if (cookie == kInvalidCookie) { + state.SkipWithError("failed to find R.layout.layout"); + return; + } + + size_t len = 0u; + const char* layout_path = + assetmanager.GetStringPoolForCookie(cookie)->string8At(value.data, &len); + if (layout_path == nullptr || len == 0u) { + state.SkipWithError("failed to lookup layout path"); + return; + } + + std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset( + StringPiece(layout_path, len).to_string(), cookie, Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(Theme_Material_Light); + + std::array<uint32_t, 92> attrs{ + {0x0101000e, 0x01010034, 0x01010095, 0x01010096, 0x01010097, 0x01010098, 0x01010099, + 0x0101009a, 0x0101009b, 0x010100ab, 0x010100af, 0x010100b0, 0x010100b1, 0x0101011f, + 0x01010120, 0x0101013f, 0x01010140, 0x0101014e, 0x0101014f, 0x01010150, 0x01010151, + 0x01010152, 0x01010153, 0x01010154, 0x01010155, 0x01010156, 0x01010157, 0x01010158, + 0x01010159, 0x0101015a, 0x0101015b, 0x0101015c, 0x0101015d, 0x0101015e, 0x0101015f, + 0x01010160, 0x01010161, 0x01010162, 0x01010163, 0x01010164, 0x01010165, 0x01010166, + 0x01010167, 0x01010168, 0x01010169, 0x0101016a, 0x0101016b, 0x0101016c, 0x0101016d, + 0x0101016e, 0x0101016f, 0x01010170, 0x01010171, 0x01010217, 0x01010218, 0x0101021d, + 0x01010220, 0x01010223, 0x01010224, 0x01010264, 0x01010265, 0x01010266, 0x010102c5, + 0x010102c6, 0x010102c7, 0x01010314, 0x01010315, 0x01010316, 0x0101035e, 0x0101035f, + 0x01010362, 0x01010374, 0x0101038c, 0x01010392, 0x01010393, 0x010103ac, 0x0101045d, + 0x010104b6, 0x010104b7, 0x010104d6, 0x010104d7, 0x010104dd, 0x010104de, 0x010104df, + 0x01010535, 0x01010536, 0x01010537, 0x01010538, 0x01010546, 0x01010567, 0x011100c9, + 0x011100ca}}; + + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0x01010084u /*def_style_attr*/, 0u /*def_style_res*/, + attrs.data(), attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyleFramework); + +} // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp index 2d73ce8f8ee3..c8dbe205fee2 100644 --- a/libs/androidfw/tests/AttributeResolution_test.cpp +++ b/libs/androidfw/tests/AttributeResolution_test.cpp @@ -21,6 +21,8 @@ #include "android-base/file.h" #include "android-base/logging.h" #include "android-base/macros.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/ResourceUtils.h" #include "TestHelpers.h" #include "data/styles/R.h" @@ -32,15 +34,14 @@ namespace android { class AttributeResolutionTest : public ::testing::Test { public: virtual void SetUp() override { - std::string contents; - ASSERT_TRUE(ReadFileFromZipToString( - GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, table_.add(contents.data(), contents.size(), - 1 /*cookie*/, true /*copyData*/)); + styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + ASSERT_NE(nullptr, styles_assets_); + assetmanager_.SetApkAssets({styles_assets_.get()}); } protected: - ResTable table_; + std::unique_ptr<const ApkAssets> styles_assets_; + AssetManager2 assetmanager_; }; class AttributeResolutionXmlTest : public AttributeResolutionTest { @@ -48,13 +49,12 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { virtual void SetUp() override { AttributeResolutionTest::SetUp(); - std::string contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", - "res/layout/layout.xml", &contents)); + std::unique_ptr<Asset> asset = + assetmanager_.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + ASSERT_NE(nullptr, asset); - ASSERT_EQ(NO_ERROR, xml_parser_.setTo(contents.data(), contents.size(), - true /*copyData*/)); + ASSERT_EQ(NO_ERROR, + xml_parser_.setTo(asset->getBuffer(true), asset->getLength(), true /*copyData*/)); // Skip to the first tag. while (xml_parser_.next() != ResXMLParser::START_TAG) { @@ -65,15 +65,50 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { ResXMLTree xml_parser_; }; +TEST(AttributeResolutionLibraryTest, ApplyStyleWithDefaultStyleResId) { + AssetManager2 assetmanager; + auto apk_assets = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/styles/styles.apk"); + ASSERT_NE(nullptr, apk_assets); + assetmanager.SetApkAssets({apk_assets.get()}); + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + + std::array<uint32_t, 2> attrs{ + {fix_package_id(R::attr::attr_one, 0x02), fix_package_id(R::attr::attr_two, 0x02)}}; + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + ApplyStyle(theme.get(), nullptr /*xml_parser*/, 0u /*def_style_attr*/, + fix_package_id(R::style::StyleOne, 0x02), attrs.data(), attrs.size(), values.data(), + indices.data()); + + const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC; + + const uint32_t* values_cursor = values.data(); + EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]); + EXPECT_EQ(1u, values_cursor[STYLE_DATA]); + EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]); + EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]); + EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]); + EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]); + + values_cursor += STYLE_NUM_ENTRIES; + EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]); + EXPECT_EQ(2u, values_cursor[STYLE_DATA]); + EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]); + EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]); + EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]); + EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]); +} + TEST_F(AttributeResolutionTest, Theme) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/, + ASSERT_TRUE(ResolveAttrs(theme.get(), 0u /*def_style_attr*/, 0u /*def_style_res*/, nullptr /*src_values*/, 0 /*src_values_length*/, attrs.data(), attrs.size(), values.data(), nullptr /*out_indices*/)); @@ -126,8 +161,8 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(), - nullptr /*out_indices*/)); + ASSERT_TRUE(RetrieveAttributes(&assetmanager_, &xml_parser_, attrs.data(), attrs.size(), + values.data(), nullptr /*out_indices*/)); uint32_t* values_cursor = values.data(); EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]); @@ -171,15 +206,15 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { } TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; std::array<uint32_t, attrs.size() + 1> indices; - ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(), + ApplyStyle(theme.get(), &xml_parser_, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), attrs.size(), values.data(), indices.data()); const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC; diff --git a/libs/androidfw/tests/BenchMain.cpp b/libs/androidfw/tests/BenchMain.cpp index 105c5f9551b7..58fc54a4a04c 100644 --- a/libs/androidfw/tests/BenchMain.cpp +++ b/libs/androidfw/tests/BenchMain.cpp @@ -18,7 +18,7 @@ #include "benchmark/benchmark.h" -#include "TestHelpers.h" +#include "BenchmarkHelpers.h" int main(int argc, char** argv) { ::benchmark::Initialize(&argc, argv); diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp index 3619b7ee83ab..faddfe599af4 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.cpp +++ b/libs/androidfw/tests/BenchmarkHelpers.cpp @@ -18,6 +18,7 @@ #include "android-base/stringprintf.h" #include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" namespace android { @@ -32,19 +33,53 @@ void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTab } } + // Make sure to force creation of the ResTable first, or else the configuration doesn't get set. + const ResTable& table = assetmanager.getResources(true); if (config != nullptr) { assetmanager.setConfiguration(*config); } - const ResTable& table = assetmanager.getResources(true); + Res_value value; + ResTable_config selected_config; + uint32_t flags; + uint32_t last_ref = 0u; + + while (state.KeepRunning()) { + ssize_t block = table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, + &selected_config); + table.resolveReference(&value, block, &last_ref, &flags, &selected_config); + } +} + +void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_config* config, + uint32_t resid, benchmark::State& state) { + std::vector<std::unique_ptr<const ApkAssets>> apk_assets; + std::vector<const ApkAssets*> apk_assets_ptrs; + for (const std::string& path : paths) { + std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path); + if (apk == nullptr) { + state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str()); + return; + } + apk_assets_ptrs.push_back(apk.get()); + apk_assets.push_back(std::move(apk)); + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets(apk_assets_ptrs); + if (config != nullptr) { + assetmanager.SetConfiguration(*config); + } Res_value value; ResTable_config selected_config; uint32_t flags; + uint32_t last_id = 0u; while (state.KeepRunning()) { - table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, - &selected_config); + ApkAssetsCookie cookie = assetmanager.GetResource( + resid, false /* may_be_bag */, 0u /* density_override */, &value, &selected_config, &flags); + assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_id); } } diff --git a/libs/androidfw/tests/BenchmarkHelpers.h b/libs/androidfw/tests/BenchmarkHelpers.h index fc366642ca36..eb0939d0e23b 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.h +++ b/libs/androidfw/tests/BenchmarkHelpers.h @@ -14,21 +14,25 @@ * limitations under the License. */ -#ifndef TESTS_BENCHMARKHELPERS_H_ -#define TESTS_BENCHMARKHELPERS_H_ +#ifndef ANDROIDFW_TESTS_BENCHMARKHELPERS_H +#define ANDROIDFW_TESTS_BENCHMARKHELPERS_H #include <string> #include <vector> +#include "androidfw/ResourceTypes.h" #include "benchmark/benchmark.h" -#include "androidfw/ResourceTypes.h" +#include "CommonHelpers.h" namespace android { void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTable_config* config, - uint32_t resid, benchmark::State& state); + uint32_t resid, ::benchmark::State& state); + +void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_config* config, + uint32_t resid, benchmark::State& state); } // namespace android -#endif /* TESTS_BENCHMARKHELPERS_H_ */ +#endif // ANDROIDFW_TESTS_BENCHMARKHELPERS_H diff --git a/libs/androidfw/tests/CommonHelpers.cpp b/libs/androidfw/tests/CommonHelpers.cpp new file mode 100644 index 000000000000..faa5350f9ecc --- /dev/null +++ b/libs/androidfw/tests/CommonHelpers.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CommonHelpers.h" + +#include <iostream> + +#include "android-base/file.h" +#include "android-base/logging.h" +#include "android-base/strings.h" + +namespace android { + +static std::string sTestDataPath; + +void InitializeTest(int* argc, char** argv) { + // Set the default test data path to be the executable path directory + data. + SetTestDataPath(base::GetExecutableDirectory() + "/tests/data"); + + for (int i = 1; i < *argc; i++) { + const std::string arg = argv[i]; + if (base::StartsWith(arg, "--testdata=")) { + SetTestDataPath(arg.substr(strlen("--testdata="))); + for (int j = i; j != *argc; j++) { + argv[j] = argv[j + 1]; + } + --(*argc); + --i; + } else if (arg == "-h" || arg == "--help") { + std::cerr << "\nAdditional options specific to this test:\n" + " --testdata=[PATH]\n" + " Specify the location of test data used within the tests.\n"; + exit(1); + } + } +} + +void SetTestDataPath(const std::string& path) { + sTestDataPath = path; +} + +const std::string& GetTestDataPath() { + CHECK(!sTestDataPath.empty()) << "no test data path set."; + return sTestDataPath; +} + +std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx) { + String8 str = pool->string8ObjectAt(idx); + return std::string(str.string(), str.length()); +} + +} // namespace android diff --git a/libs/androidfw/tests/CommonHelpers.h b/libs/androidfw/tests/CommonHelpers.h new file mode 100644 index 000000000000..c160fbba4c01 --- /dev/null +++ b/libs/androidfw/tests/CommonHelpers.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROIDFW_TEST_COMMON_HELPERS_H +#define ANDROIDFW_TEST_COMMON_HELPERS_H + +#include <ostream> +#include <string> + +#include "androidfw/ResourceTypes.h" +#include "utils/String16.h" +#include "utils/String8.h" + +namespace android { + +void InitializeTest(int* argc, char** argv); + +enum { MAY_NOT_BE_BAG = false }; + +void SetTestDataPath(const std::string& path); + +const std::string& GetTestDataPath(); + +std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx); + +static inline bool operator==(const ResTable_config& a, const ResTable_config& b) { + return a.compare(b) == 0; +} + +static inline ::std::ostream& operator<<(::std::ostream& out, const String8& str) { + return out << str.string(); +} + +static inline ::std::ostream& operator<<(::std::ostream& out, const String16& str) { + return out << String8(str).string(); +} + +static inline ::std::ostream& operator<<(::std::ostream& out, const ResTable_config& c) { + return out << c.toString(); +} + +} // namespace android + +#endif // ANDROIDFW_TEST_COMMON_HELPERS_H diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp index 86a627e1485d..ac08c52772d1 100644 --- a/libs/androidfw/tests/ConfigLocale_test.cpp +++ b/libs/androidfw/tests/ConfigLocale_test.cpp @@ -173,6 +173,18 @@ TEST(ConfigLocaleTest, IsMoreSpecificThan) { fillIn("en", "US", NULL, "POSIX", &r); EXPECT_FALSE(l.isMoreSpecificThan(r)); EXPECT_TRUE(r.isMoreSpecificThan(l)); + + fillIn("ar", "EG", NULL, NULL, &l); + fillIn("ar", "EG", NULL, NULL, &r); + memcpy(&r.localeNumberingSystem, "latn", 4); + EXPECT_FALSE(l.isMoreSpecificThan(r)); + EXPECT_TRUE(r.isMoreSpecificThan(l)); + + fillIn("en", "US", NULL, NULL, &l); + fillIn("es", "ES", NULL, NULL, &r); + + EXPECT_FALSE(l.isMoreSpecificThan(r)); + EXPECT_FALSE(r.isMoreSpecificThan(l)); } TEST(ConfigLocaleTest, setLocale) { @@ -185,6 +197,7 @@ TEST(ConfigLocaleTest, setLocale) { EXPECT_TRUE(test.localeScriptWasComputed); EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4)); EXPECT_EQ(0, test.localeVariant[0]); + EXPECT_EQ(0, test.localeNumberingSystem[0]); test.setBcp47Locale("eng-419"); char out[4] = {1, 1, 1, 1}; @@ -198,6 +211,7 @@ TEST(ConfigLocaleTest, setLocale) { EXPECT_EQ('4', out[0]); EXPECT_EQ('1', out[1]); EXPECT_EQ('9', out[2]); + EXPECT_EQ(0, test.localeNumberingSystem[0]); test.setBcp47Locale("en-Latn-419"); EXPECT_EQ('e', test.language[0]); @@ -209,6 +223,7 @@ TEST(ConfigLocaleTest, setLocale) { EXPECT_EQ('4', out[0]); EXPECT_EQ('1', out[1]); EXPECT_EQ('9', out[2]); + EXPECT_EQ(0, test.localeNumberingSystem[0]); test.setBcp47Locale("de-1901"); memset(out, 1, 4); @@ -222,6 +237,7 @@ TEST(ConfigLocaleTest, setLocale) { test.unpackRegion(out); EXPECT_EQ('\0', out[0]); EXPECT_EQ(0, strcmp("1901", test.localeVariant)); + EXPECT_EQ(0, test.localeNumberingSystem[0]); test.setBcp47Locale("de-Latn-1901"); memset(out, 1, 4); @@ -235,6 +251,44 @@ TEST(ConfigLocaleTest, setLocale) { test.unpackRegion(out); EXPECT_EQ('\0', out[0]); EXPECT_EQ(0, strcmp("1901", test.localeVariant)); + EXPECT_EQ(0, test.localeNumberingSystem[0]); + + test.setBcp47Locale("ar-EG-u-nu-latn"); + EXPECT_EQ('a', test.language[0]); + EXPECT_EQ('r', test.language[1]); + EXPECT_EQ('E', test.country[0]); + EXPECT_EQ('G', test.country[1]); + EXPECT_TRUE(test.localeScriptWasComputed); + EXPECT_EQ(0, memcmp("Arab", test.localeScript, 4)); + EXPECT_EQ(0, test.localeVariant[0]); + EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4)); + + test.setBcp47Locale("ar-EG-u"); + EXPECT_EQ(0, test.localeNumberingSystem[0]); + + test.setBcp47Locale("ar-EG-u-nu"); + EXPECT_EQ(0, test.localeNumberingSystem[0]); + + test.setBcp47Locale("ar-EG-u-attr-nu-latn"); + EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4)); + + test.setBcp47Locale("ar-EG-u-ca-gregory-nu-latn"); + EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4)); + + test.setBcp47Locale("ar-EG-u-nu-latn-ca-gregory"); + EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4)); + + test.setBcp47Locale("ar-EG-u-nu-toolongnumsys"); + EXPECT_EQ(0, test.localeNumberingSystem[0]); + + test.setBcp47Locale("ar-EG-u-nu-latn-nu-arab"); + EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4)); + + test.setBcp47Locale("ar-EG-u-co-nu-latn"); + EXPECT_EQ(0, test.localeNumberingSystem[0]); + + test.setBcp47Locale("ar-u-co-abcd-attr-nu-latn"); + EXPECT_EQ(0, test.localeNumberingSystem[0]); } TEST(ConfigLocaleTest, computeScript) { @@ -279,6 +333,22 @@ TEST(ConfigLocaleTest, getBcp47Locale_script) { EXPECT_EQ(0, strcmp("en", out)); } +TEST(ConfigLocaleTest, getBcp47Locale_numberingSystem) { + ResTable_config config; + fillIn("en", NULL, NULL, NULL, &config); + + char out[RESTABLE_MAX_LOCALE_LEN]; + + memcpy(&config.localeNumberingSystem, "latn", 4); + config.getBcp47Locale(out); + EXPECT_EQ(0, strcmp("en-u-nu-latn", out)); + + fillIn("sr", "SR", "Latn", NULL, &config); + memcpy(&config.localeNumberingSystem, "latn", 4); + config.getBcp47Locale(out); + EXPECT_EQ(0, strcmp("sr-Latn-SR-u-nu-latn", out)); +} + TEST(ConfigLocaleTest, getBcp47Locale_canonicalize) { ResTable_config config; char out[RESTABLE_MAX_LOCALE_LEN]; @@ -391,6 +461,11 @@ TEST(ConfigLocaleTest, match) { fillIn("ar", "XB", NULL, NULL, &requested); // Even if they are pseudo-locales, exactly equal locales match. EXPECT_TRUE(supported.match(requested)); + + fillIn("ar", "EG", NULL, NULL, &supported); + fillIn("ar", "TN", NULL, NULL, &requested); + memcpy(&supported.localeNumberingSystem, "latn", 4); + EXPECT_TRUE(supported.match(requested)); } TEST(ConfigLocaleTest, match_emptyScript) { @@ -716,6 +791,26 @@ TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) { EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); } +TEST(ConfigLocaleTest, isLocaleBetterThan_numberingSystem) { + ResTable_config config1, config2, request; + + fillIn("ar", "EG", NULL, NULL, &request); + memcpy(&request.localeNumberingSystem, "latn", 4); + fillIn("ar", NULL, NULL, NULL, &config1); + memcpy(&config1.localeNumberingSystem, "latn", 4); + fillIn("ar", NULL, NULL, NULL, &config2); + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("ar", "EG", NULL, NULL, &request); + memcpy(&request.localeNumberingSystem, "latn", 4); + fillIn("ar", "TN", NULL, NULL, &config1); + memcpy(&config1.localeNumberingSystem, "latn", 4); + fillIn("ar", NULL, NULL, NULL, &config2); + EXPECT_TRUE(config2.isLocaleBetterThan(config1, &request)); + EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); +} + // Default resources are considered better matches for US English // and US-like English locales than International English locales TEST(ConfigLocaleTest, isLocaleBetterThan_UsEnglishIsSpecial) { diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index d12be184745c..9eb4a13f34d1 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -22,7 +22,7 @@ #include "TestHelpers.h" #include "data/basic/R.h" -using com::android::basic::R; +using ::com::android::basic::R; namespace android { diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index 756869f6377d..cae632ddea30 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -16,14 +16,27 @@ #include "androidfw/LoadedArsc.h" +#include "android-base/file.h" +#include "androidfw/ResourceUtils.h" + #include "TestHelpers.h" #include "data/basic/R.h" #include "data/libclient/R.h" +#include "data/sparse/R.h" #include "data/styles/R.h" namespace app = com::android::app; namespace basic = com::android::basic; namespace libclient = com::android::libclient; +namespace sparse = com::android::sparse; + +using ::android::base::ReadFileToString; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; namespace android { @@ -32,49 +45,49 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents)); - std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(contents.data(), contents.size()); - ASSERT_NE(nullptr, loaded_arsc); + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_THAT(loaded_arsc, NotNull()); - const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(app::R::string::string_one)); + ASSERT_THAT(package, NotNull()); + EXPECT_THAT(package->GetPackageName(), StrEq("com.android.app")); + EXPECT_THAT(package->GetPackageId(), Eq(0x7f)); - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 24; + const uint8_t type_index = get_type_id(app::R::string::string_one) - 1; + const uint16_t entry_index = get_entry_id(app::R::string::string_one); - LoadedArscEntry entry; - ResTable_config selected_config; - uint32_t flags; + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); - ASSERT_TRUE( - loaded_arsc->FindEntry(app::R::string::string_one, config, &entry, &selected_config, &flags)); - ASSERT_NE(nullptr, entry.entry); + const ResTable_type* type = type_spec->types[0]; + ASSERT_THAT(type, NotNull()); + ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull()); } -TEST(LoadedArscTest, FindDefaultEntry) { +TEST(LoadedArscTest, LoadSparseEntryApp) { std::string contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc", + &contents)); - std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(contents.data(), contents.size()); - ASSERT_NE(nullptr, loaded_arsc); + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9)); + ASSERT_THAT(package, NotNull()); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); - desired_config.language[0] = 'd'; - desired_config.language[1] = 'e'; + const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1; + const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9); - LoadedArscEntry entry; - ResTable_config selected_config; - uint32_t flags; + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); - ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry, - &selected_config, &flags)); - ASSERT_NE(nullptr, entry.entry); + const ResTable_type* type = type_spec->types[0]; + ASSERT_THAT(type, NotNull()); + ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull()); } TEST(LoadedArscTest, LoadSharedLibrary) { @@ -82,16 +95,14 @@ TEST(LoadedArscTest, LoadSharedLibrary) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc", &contents)); - std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(contents.data(), contents.size()); - ASSERT_NE(nullptr, loaded_arsc); + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName()); - EXPECT_EQ(0, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); @@ -104,27 +115,24 @@ TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/libclient/libclient.apk", "resources.arsc", &contents)); - std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(contents.data(), contents.size()); - ASSERT_NE(nullptr, loaded_arsc); + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_FALSE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); // The library has two dependencies. - ASSERT_EQ(2u, dynamic_pkg_map.size()); + ASSERT_THAT(dynamic_pkg_map, SizeIs(2u)); + EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one")); + EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02)); - EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name); - EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id); - - EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name); - EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id); + EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two")); + EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03)); } TEST(LoadedArscTest, LoadAppAsSharedLibrary) { @@ -132,46 +140,137 @@ TEST(LoadedArscTest, LoadAppAsSharedLibrary) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/appaslib/appaslib.apk", "resources.arsc", &contents)); - std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load( - contents.data(), contents.size(), false /*system*/, true /*load_as_shared_library*/); - ASSERT_NE(nullptr, loaded_arsc); + std::unique_ptr<const LoadedArsc> loaded_arsc = + LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/, + true /*load_as_shared_library*/); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); } TEST(LoadedArscTest, LoadFeatureSplit) { std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc", &contents)); - std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(contents.data(), contents.size()); - ASSERT_NE(nullptr, loaded_arsc); + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_THAT(loaded_arsc, NotNull()); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3)); + ASSERT_THAT(package, NotNull()); - LoadedArscEntry entry; - ResTable_config selected_config; - uint32_t flags; + uint8_t type_index = get_type_id(basic::R::string::test3) - 1; + uint8_t entry_index = get_entry_id(basic::R::string::test3); - ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry, - &selected_config, &flags)); + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); size_t len; - const char16_t* type_name16 = entry.type_string_ref.string16(&len); - ASSERT_NE(nullptr, type_name16); - ASSERT_NE(0u, len); + const char16_t* type_name16 = + package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len); + ASSERT_THAT(type_name16, NotNull()); + EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string")); + + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull()); +} + +// AAPT(2) generates resource tables with chunks in a certain order. The rule is that +// a RES_TABLE_TYPE_TYPE with id `i` must always be preceded by a RES_TABLE_TYPE_SPEC_TYPE with +// id `i`. The RES_TABLE_TYPE_SPEC_TYPE does not need to be directly preceding, however. +// +// AAPT(2) generates something like: +// RES_TABLE_TYPE_SPEC_TYPE id=1 +// RES_TABLE_TYPE_TYPE id=1 +// RES_TABLE_TYPE_SPEC_TYPE id=2 +// RES_TABLE_TYPE_TYPE id=2 +// +// But the following is valid too: +// RES_TABLE_TYPE_SPEC_TYPE id=1 +// RES_TABLE_TYPE_SPEC_TYPE id=2 +// RES_TABLE_TYPE_TYPE id=1 +// RES_TABLE_TYPE_TYPE id=2 +// +TEST(LoadedArscTest, LoadOutOfOrderTypeSpecs) { + std::string contents; + ASSERT_TRUE( + ReadFileFromZipToString(GetTestDataPath() + "/out_of_order_types/out_of_order_types.apk", + "resources.arsc", &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_THAT(loaded_arsc, NotNull()); + + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); + const auto& package = loaded_arsc->GetPackages()[0]; + ASSERT_THAT(package, NotNull()); - size_t utf8_len = utf16_to_utf8_length(type_name16, len); - std::string type_name; - type_name.resize(utf8_len); - utf16_to_utf8(type_name16, len, &*type_name.begin(), utf8_len + 1); + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); - EXPECT_EQ(std::string("string"), type_name); + type_spec = package->GetTypeSpecByTypeIndex(1); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); +} + +class MockLoadedIdmap : public LoadedIdmap { + public: + MockLoadedIdmap() : LoadedIdmap() { + local_header_.magic = kIdmapMagic; + local_header_.version = kIdmapCurrentVersion; + local_header_.target_package_id = 0x08; + local_header_.type_count = 1; + header_ = &local_header_; + + entry_header = util::unique_cptr<IdmapEntry_header>( + (IdmapEntry_header*)::malloc(sizeof(IdmapEntry_header) + sizeof(uint32_t))); + entry_header->target_type_id = 0x03; + entry_header->overlay_type_id = 0x02; + entry_header->entry_id_offset = 1; + entry_header->entry_count = 1; + entry_header->entries[0] = 0x00000000u; + type_map_[entry_header->overlay_type_id] = entry_header.get(); + } + + private: + Idmap_header local_header_; + util::unique_cptr<IdmapEntry_header> entry_header; +}; + +TEST(LoadedArscTest, LoadOverlay) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc", + &contents)); + + MockLoadedIdmap loaded_idmap; + + std::unique_ptr<const LoadedArsc> loaded_arsc = + LoadedArsc::Load(StringPiece(contents), &loaded_idmap); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u); + ASSERT_THAT(package, NotNull()); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); + + // The entry being overlaid doesn't exist at the original entry index. + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull()); + + // Since this is an overlay, the actual entry ID must be mapped. + ASSERT_THAT(type_spec->idmap_entries, NotNull()); + uint16_t target_entry_id = 0u; + ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id)); + ASSERT_THAT(target_entry_id, Eq(0x0u)); + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull()); } // structs with size fields (like Res_value, ResTable_entry) should be diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp index 2df41305237e..326474e16e5d 100644 --- a/libs/androidfw/tests/ResTable_test.cpp +++ b/libs/androidfw/tests/ResTable_test.cpp @@ -424,4 +424,56 @@ TEST(ResTableTest, GetConfigurationsReturnsUniqueList) { EXPECT_EQ(1, std::count(locales.begin(), locales.end(), String8("sv"))); } +TEST(ResTableTest, TruncatedEncodeLength) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_valid.apk", + "resources.arsc", &contents)); + + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size())); + + Res_value val; + ssize_t block = table.getResource(0x7f010001, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_STRING, val.dataType); + + const ResStringPool* pool = table.getTableStringBlock(block); + ASSERT_TRUE(pool != NULL); + ASSERT_LT(val.data, pool->size()); + + // Make sure a string with a truncated length is read to its correct length + size_t str_len; + const char* target_str8 = pool->string8At(val.data, &str_len); + ASSERT_TRUE(target_str8 != NULL); + ASSERT_EQ(size_t(40076), String8(target_str8, str_len).size()); + ASSERT_EQ(target_str8[40075], ']'); + + const char16_t* target_str16 = pool->stringAt(val.data, &str_len); + ASSERT_TRUE(target_str16 != NULL); + ASSERT_EQ(size_t(40076), String16(target_str16, str_len).size()); + ASSERT_EQ(target_str8[40075], (char16_t) ']'); + + // Load an edited apk with the null terminator removed from the end of the + // string + std::string invalid_contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_invalid.apk", + "resources.arsc", &invalid_contents)); + ResTable invalid_table; + ASSERT_EQ(NO_ERROR, invalid_table.add(invalid_contents.data(), invalid_contents.size())); + + Res_value invalid_val; + ssize_t invalid_block = invalid_table.getResource(0x7f010001, &invalid_val, MAY_NOT_BE_BAG); + ASSERT_GE(invalid_block, 0); + ASSERT_EQ(Res_value::TYPE_STRING, invalid_val.dataType); + + const ResStringPool* invalid_pool = invalid_table.getTableStringBlock(invalid_block); + ASSERT_TRUE(invalid_pool != NULL); + ASSERT_LT(invalid_val.data, invalid_pool->size()); + + // Make sure a string with a truncated length that is not null terminated errors + // and does not return the string + ASSERT_TRUE(invalid_pool->string8At(invalid_val.data, &str_len) == NULL); + ASSERT_TRUE(invalid_pool->stringAt(invalid_val.data, &str_len) == NULL); +} + } // namespace android diff --git a/libs/androidfw/tests/ResourceUtils_test.cpp b/libs/androidfw/tests/ResourceUtils_test.cpp index b64a884fb8fb..a02f166b5586 100644 --- a/libs/androidfw/tests/ResourceUtils_test.cpp +++ b/libs/androidfw/tests/ResourceUtils_test.cpp @@ -27,23 +27,48 @@ TEST(ResourceUtilsTest, ExtractResourceName) { EXPECT_EQ("string", type); EXPECT_EQ("foo", entry); + ASSERT_TRUE(ExtractResourceName("@android:string/foo", &package, &type, &entry)); + EXPECT_EQ("android", package); + EXPECT_EQ("string", type); + EXPECT_EQ("foo", entry); + ASSERT_TRUE(ExtractResourceName("string/foo", &package, &type, &entry)); EXPECT_EQ("", package); EXPECT_EQ("string", type); EXPECT_EQ("foo", entry); + ASSERT_TRUE(ExtractResourceName("@string/foo", &package, &type, &entry)); + EXPECT_EQ("", package); + EXPECT_EQ("string", type); + EXPECT_EQ("foo", entry); + ASSERT_TRUE(ExtractResourceName("foo", &package, &type, &entry)); EXPECT_EQ("", package); EXPECT_EQ("", type); EXPECT_EQ("foo", entry); + ASSERT_TRUE(ExtractResourceName("@foo", &package, &type, &entry)); + EXPECT_EQ("", package); + EXPECT_EQ("", type); + EXPECT_EQ("foo", entry); + ASSERT_TRUE(ExtractResourceName("android:foo", &package, &type, &entry)); EXPECT_EQ("android", package); EXPECT_EQ("", type); EXPECT_EQ("foo", entry); +// ASSERT_TRUE(ExtractResourceName("@android:foo", &package, &type, &entry)); +// EXPECT_EQ("android", package); +// EXPECT_EQ("", type); +// EXPECT_EQ("foo", entry); + EXPECT_FALSE(ExtractResourceName(":string/foo", &package, &type, &entry)); + + EXPECT_FALSE(ExtractResourceName("@:string/foo", &package, &type, &entry)); + EXPECT_FALSE(ExtractResourceName("/foo", &package, &type, &entry)); + + EXPECT_FALSE(ExtractResourceName("@/foo", &package, &type, &entry)); } } // namespace android diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp index 1ebf7ce623bd..c9b4ad8af278 100644 --- a/libs/androidfw/tests/SparseEntry_bench.cpp +++ b/libs/androidfw/tests/SparseEntry_bench.cpp @@ -18,47 +18,46 @@ #include "androidfw/ResourceTypes.h" #include "BenchmarkHelpers.h" -#include "TestHelpers.h" #include "data/sparse/R.h" namespace sparse = com::android::sparse; namespace android { -static void BM_SparseEntryGetResourceSparseSmall(benchmark::State& state) { +static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) { ResTable_config config; memset(&config, 0, sizeof(config)); config.sdkVersion = 26; - GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, - sparse::R::integer::foo_9, state); + GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state); } -BENCHMARK(BM_SparseEntryGetResourceSparseSmall); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999); -static void BM_SparseEntryGetResourceNotSparseSmall(benchmark::State& state) { +static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) { ResTable_config config; memset(&config, 0, sizeof(config)); config.sdkVersion = 26; - GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, - sparse::R::integer::foo_9, state); + GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state); } -BENCHMARK(BM_SparseEntryGetResourceNotSparseSmall); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999); -static void BM_SparseEntryGetResourceSparseLarge(benchmark::State& state) { +static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) { ResTable_config config; memset(&config, 0, sizeof(config)); config.sdkVersion = 26; - GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, - sparse::R::string::foo_999, state); + GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state); } -BENCHMARK(BM_SparseEntryGetResourceSparseLarge); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999); -static void BM_SparseEntryGetResourceNotSparseLarge(benchmark::State& state) { +static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) { ResTable_config config; memset(&config, 0, sizeof(config)); config.sdkVersion = 26; - GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, - sparse::R::string::foo_999, state); + GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state); } -BENCHMARK(BM_SparseEntryGetResourceNotSparseLarge); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9); +BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999); } // namespace android diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp index 1e763a5e53a8..9e320a21b534 100644 --- a/libs/androidfw/tests/TestHelpers.cpp +++ b/libs/androidfw/tests/TestHelpers.cpp @@ -16,67 +16,22 @@ #include "TestHelpers.h" -#include <libgen.h> -#include <unistd.h> - -#include <memory> -#include <string> - -#include "android-base/file.h" -#include "android-base/logging.h" -#include "android-base/strings.h" #include "ziparchive/zip_archive.h" -namespace android { - -static std::string sTestDataPath; +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; -// Extract the directory of the current executable path. -static std::string GetExecutableDir() { - const std::string path = base::GetExecutablePath(); - std::unique_ptr<char, decltype(&std::free)> mutable_path = {strdup(path.c_str()), std::free}; - std::string executable_dir = dirname(mutable_path.get()); - return executable_dir; -} - -void InitializeTest(int* argc, char** argv) { - // Set the default test data path to be the executable path directory. - SetTestDataPath(GetExecutableDir()); - - for (int i = 1; i < *argc; i++) { - const std::string arg = argv[i]; - if (base::StartsWith(arg, "--testdata=")) { - SetTestDataPath(arg.substr(strlen("--testdata="))); - for (int j = i; j != *argc; j++) { - argv[j] = argv[j + 1]; - } - --(*argc); - --i; - } else if (arg == "-h" || arg == "--help") { - std::cerr << "\nAdditional options specific to this test:\n" - " --testdata=[PATH]\n" - " Specify the location of test data used within the tests.\n"; - exit(1); - } - } -} - -void SetTestDataPath(const std::string& path) { sTestDataPath = path; } - -const std::string& GetTestDataPath() { - CHECK(!sTestDataPath.empty()) << "no test data path set."; - return sTestDataPath; -} +namespace android { -::testing::AssertionResult ReadFileFromZipToString(const std::string& zip_path, - const std::string& file, - std::string* out_contents) { +AssertionResult ReadFileFromZipToString(const std::string& zip_path, const std::string& file, + std::string* out_contents) { out_contents->clear(); ::ZipArchiveHandle handle; int32_t result = OpenArchive(zip_path.c_str(), &handle); if (result != 0) { - return ::testing::AssertionFailure() << "Failed to open zip '" << zip_path - << "': " << ::ErrorCodeString(result); + return AssertionFailure() << "Failed to open zip '" << zip_path + << "': " << ::ErrorCodeString(result); } ::ZipString name(file.c_str()); @@ -84,8 +39,8 @@ const std::string& GetTestDataPath() { result = ::FindEntry(handle, name, &entry); if (result != 0) { ::CloseArchive(handle); - return ::testing::AssertionFailure() << "Could not find file '" << file << "' in zip '" - << zip_path << "' : " << ::ErrorCodeString(result); + return AssertionFailure() << "Could not find file '" << file << "' in zip '" << zip_path + << "' : " << ::ErrorCodeString(result); } out_contents->resize(entry.uncompressed_length); @@ -94,41 +49,36 @@ const std::string& GetTestDataPath() { out_contents->size()); if (result != 0) { ::CloseArchive(handle); - return ::testing::AssertionFailure() << "Failed to extract file '" << file << "' from zip '" - << zip_path << "': " << ::ErrorCodeString(result); + return AssertionFailure() << "Failed to extract file '" << file << "' from zip '" << zip_path + << "': " << ::ErrorCodeString(result); } ::CloseArchive(handle); - return ::testing::AssertionSuccess(); + return AssertionSuccess(); } -::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id, - const char* expected_str) { +AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id, + const char* expected_str) { Res_value val; ssize_t block = table.getResource(resource_id, &val, MAY_NOT_BE_BAG); if (block < 0) { - return ::testing::AssertionFailure() << "could not find resource"; + return AssertionFailure() << "could not find resource"; } if (val.dataType != Res_value::TYPE_STRING) { - return ::testing::AssertionFailure() << "resource is not a string"; + return AssertionFailure() << "resource is not a string"; } const ResStringPool* pool = table.getTableStringBlock(block); if (pool == NULL) { - return ::testing::AssertionFailure() << "table has no string pool for block " << block; + return AssertionFailure() << "table has no string pool for block " << block; } const String8 actual_str = pool->string8ObjectAt(val.data); if (String8(expected_str) != actual_str) { - return ::testing::AssertionFailure() << actual_str.string(); + return AssertionFailure() << actual_str.string(); } - return ::testing::AssertionSuccess() << actual_str.string(); -} - -std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx) { - String8 str = pool->string8ObjectAt(idx); - return std::string(str.string(), str.length()); + return AssertionSuccess() << actual_str.string(); } } // namespace android diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h index ec78b2ae5efc..df0c642f4565 100644 --- a/libs/androidfw/tests/TestHelpers.h +++ b/libs/androidfw/tests/TestHelpers.h @@ -14,53 +14,26 @@ * limitations under the License. */ -#ifndef TEST_HELPERS_H_ -#define TEST_HELPERS_H_ +#ifndef ANDROIDFW_TEST_TESTHELPERS_H +#define ANDROIDFW_TEST_TESTHELPERS_H -#include <ostream> #include <string> -#include <vector> #include "androidfw/ResourceTypes.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" -#include "utils/String16.h" -#include "utils/String8.h" -static inline ::std::ostream& operator<<(::std::ostream& out, const android::String8& str) { - return out << str.string(); -} - -static inline ::std::ostream& operator<<(::std::ostream& out, const android::String16& str) { - return out << android::String8(str).string(); -} +#include "CommonHelpers.h" namespace android { -void InitializeTest(int* argc, char** argv); - -enum { MAY_NOT_BE_BAG = false }; - -void SetTestDataPath(const std::string& path); - -const std::string& GetTestDataPath(); - ::testing::AssertionResult ReadFileFromZipToString(const std::string& zip_path, const std::string& file, std::string* out_contents); -static inline bool operator==(const ResTable_config& a, const ResTable_config& b) { - return a.compare(b) == 0; -} - -static inline ::std::ostream& operator<<(::std::ostream& out, const ResTable_config& c) { - return out << c.toString().string(); -} - ::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id, const char* expected_str); -std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx); - } // namespace android -#endif // TEST_HELPERS_H_ +#endif // ANDROIDFW_TEST_TESTHELPERS_H diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index dfff9c00922c..55d53edf6a2b 100644 --- a/libs/androidfw/tests/Theme_test.cpp +++ b/libs/androidfw/tests/Theme_test.cpp @@ -23,6 +23,7 @@ #include "data/lib_one/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; @@ -33,6 +34,9 @@ namespace android { class ThemeTest : public ::testing::Test { public: void SetUp() override { + system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/); + ASSERT_NE(nullptr, system_assets_); + style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); ASSERT_NE(nullptr, style_assets_); @@ -47,6 +51,7 @@ class ThemeTest : public ::testing::Test { } protected: + std::unique_ptr<const ApkAssets> system_assets_; std::unique_ptr<const ApkAssets> style_assets_; std::unique_ptr<const ApkAssets> libclient_assets_; std::unique_ptr<const ApkAssets> lib_one_assets_; @@ -123,6 +128,18 @@ TEST_F(ThemeTest, SingleThemeWithParent) { EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags); } +TEST_F(ThemeTest, TryToUseBadResourceId) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({style_assets_.get()}); + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo)); + + Res_value value; + uint32_t flags; + ASSERT_EQ(kInvalidCookie, theme->GetAttribute(0x7f000001, &value, &flags)); +} + TEST_F(ThemeTest, MultipleThemesOverlaidNotForce) { AssetManager2 assetmanager; assetmanager.SetApkAssets({style_assets_.get()}); @@ -262,20 +279,30 @@ TEST_F(ThemeTest, CopyThemeSameAssetManager) { EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags); } -TEST_F(ThemeTest, FailToCopyThemeWithDifferentAssetManager) { +TEST_F(ThemeTest, OnlyCopySystemThemeWhenAssetManagersDiffer) { AssetManager2 assetmanager_one; - assetmanager_one.SetApkAssets({style_assets_.get()}); + assetmanager_one.SetApkAssets({system_assets_.get(), style_assets_.get()}); AssetManager2 assetmanager_two; - assetmanager_two.SetApkAssets({style_assets_.get()}); + assetmanager_two.SetApkAssets({system_assets_.get(), style_assets_.get()}); auto theme_one = assetmanager_one.NewTheme(); ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne)); auto theme_two = assetmanager_two.NewTheme(); + ASSERT_TRUE(theme_two->ApplyStyle(R::style::Theme_One)); ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleTwo)); - EXPECT_FALSE(theme_one->SetTo(*theme_two)); + EXPECT_TRUE(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)); + + // Only system. + EXPECT_NE(kInvalidCookie, theme_one->GetAttribute(R::attr::foreground, &value, &flags)); } } // namespace android diff --git a/libs/androidfw/tests/data/app/app.apk b/libs/androidfw/tests/data/app/app.apk Binary files differindex ccb08242a656..c8ad86ded851 100644 --- a/libs/androidfw/tests/data/app/app.apk +++ b/libs/androidfw/tests/data/app/app.apk diff --git a/libs/androidfw/tests/data/app/assets/app_file.txt b/libs/androidfw/tests/data/app/assets/app_file.txt new file mode 100644 index 000000000000..b214e06d6ece --- /dev/null +++ b/libs/androidfw/tests/data/app/assets/app_file.txt @@ -0,0 +1 @@ +app file diff --git a/libs/androidfw/tests/data/app/assets/file.txt b/libs/androidfw/tests/data/app/assets/file.txt new file mode 100644 index 000000000000..081154272520 --- /dev/null +++ b/libs/androidfw/tests/data/app/assets/file.txt @@ -0,0 +1 @@ +app override file diff --git a/libs/androidfw/tests/data/app/build b/libs/androidfw/tests/data/app/build index d418158c547b..09af842e70fb 100755 --- a/libs/androidfw/tests/data/app/build +++ b/libs/androidfw/tests/data/app/build @@ -17,4 +17,11 @@ set -e -aapt package -I ../system/system.apk -M AndroidManifest.xml -S res -F app.apk -f +aapt2 compile --dir res -o compiled.flata +aapt2 link \ + --manifest AndroidManifest.xml \ + -I ../system/system.apk \ + -A assets \ + -o app.apk \ + compiled.flata +rm compiled.flata diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h index 94a2a14ced87..b7e814fea079 100644 --- a/libs/androidfw/tests/data/basic/R.h +++ b/libs/androidfw/tests/data/basic/R.h @@ -34,6 +34,7 @@ struct R { struct layout { enum : uint32_t { main = 0x7f020000, + layoutt = 0x7f020001, }; }; @@ -55,6 +56,7 @@ struct R { number2 = 0x7f040001, ref1 = 0x7f040002, ref2 = 0x7f040003, + deep_ref = 0x7f040004, // From feature number3 = 0x80030000, diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk Binary files differindex 0c17328e86b2..b721ebfde445 100644 --- a/libs/androidfw/tests/data/basic/basic.apk +++ b/libs/androidfw/tests/data/basic/basic.apk diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk Binary files differindex e45258c6a005..767dff6fcfa5 100644 --- a/libs/androidfw/tests/data/basic/basic_de_fr.apk +++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk Binary files differindex 4ae1a7c87a70..58953f56d736 100644 --- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk Binary files differindex a240d4c06c1d..103f6565bb06 100644 --- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk Binary files differindex fd3d9b233084..61369d506786 100644 --- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build index aedebb6a5c43..c0b22d804558 100755 --- a/libs/androidfw/tests/data/basic/build +++ b/libs/androidfw/tests/data/basic/build @@ -19,11 +19,15 @@ set -e PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar -aapt package \ - -M AndroidManifest.xml \ - -S res \ - -A assets \ +aapt2 compile --dir res -o compiled.flata +aapt2 link \ -I $PATH_TO_FRAMEWORK_RES \ - --split hdpi --split xhdpi --split xxhdpi --split fr,de \ - -F basic.apk \ - -f + --manifest AndroidManifest.xml \ + -A assets \ + --split basic_hdpi-v4.apk:hdpi \ + --split basic_xhdpi-v4.apk:xhdpi \ + --split basic_xxhdpi-v4.apk:xxhdpi \ + --split basic_de_fr.apk:de,fr \ + -o basic.apk \ + compiled.flata +rm compiled.flata diff --git a/libs/androidfw/tests/data/basic/res/layout/layout.xml b/libs/androidfw/tests/data/basic/res/layout/layout.xml new file mode 100644 index 000000000000..045ede454bca --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/layout/layout.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<Button xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/ok" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginStart="2dip" + android:layout_marginEnd="2dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:text="@android:string/ok" />
\ No newline at end of file diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml index 638c9832ab4c..d4b2683c62e1 100644 --- a/libs/androidfw/tests/data/basic/res/values/values.xml +++ b/libs/androidfw/tests/data/basic/res/values/values.xml @@ -22,6 +22,7 @@ <attr name="attr2" format="reference|integer" /> <public type="layout" name="main" id="0x7f020000" /> + <public type="layout" name="layout" id="0x7f020001" /> <public type="string" name="test1" id="0x7f030000" /> <string name="test1">test1</string> @@ -43,6 +44,18 @@ <public type="integer" name="ref2" id="0x7f040003" /> <integer name="ref2">12000</integer> + <public type="integer" name="deep_ref" id="0x7f040004" /> + <integer name="deep_ref">@integer/deep_ref_1</integer> + <integer name="deep_ref_1">@integer/deep_ref_2</integer> + <integer name="deep_ref_2">@integer/deep_ref_3</integer> + <integer name="deep_ref_3">@integer/deep_ref_4</integer> + <integer name="deep_ref_4">@integer/deep_ref_5</integer> + <integer name="deep_ref_5">@integer/deep_ref_6</integer> + <integer name="deep_ref_6">@integer/deep_ref_7</integer> + <integer name="deep_ref_7">@integer/deep_ref_8</integer> + <integer name="deep_ref_8">@integer/deep_ref_9</integer> + <integer name="deep_ref_9">100</integer> + <public type="style" name="Theme1" id="0x7f050000" /> <style name="Theme1"> <item name="com.android.basic:attr1">100</item> @@ -60,4 +73,13 @@ <item>2</item> <item>3</item> </integer-array> + + <overlayable> + <item type="string" name="test2" /> + <item type="array" name="integerArray1" /> + </overlayable> + + <item name="high_ref" type="id">@id/middle_ref</item> + <item name="middle_ref" type="id">@id/low_ref</item> + <item name="low_ref" type="id"/> </resources> diff --git a/libs/androidfw/tests/data/length_decode/length_decode_invalid.apk b/libs/androidfw/tests/data/length_decode/length_decode_invalid.apk Binary files differnew file mode 100644 index 000000000000..b089651e6786 --- /dev/null +++ b/libs/androidfw/tests/data/length_decode/length_decode_invalid.apk diff --git a/libs/androidfw/tests/data/length_decode/length_decode_valid.apk b/libs/androidfw/tests/data/length_decode/length_decode_valid.apk Binary files differnew file mode 100644 index 000000000000..add23e1ee6d4 --- /dev/null +++ b/libs/androidfw/tests/data/length_decode/length_decode_valid.apk diff --git a/libs/androidfw/tests/data/out_of_order_types/AndroidManifest.xml b/libs/androidfw/tests/data/out_of_order_types/AndroidManifest.xml new file mode 100644 index 000000000000..34016db8b808 --- /dev/null +++ b/libs/androidfw/tests/data/out_of_order_types/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.app" /> diff --git a/libs/androidfw/Android.mk b/libs/androidfw/tests/data/out_of_order_types/build index 68c51effd79d..8496f81038b0 100644..100755 --- a/libs/androidfw/Android.mk +++ b/libs/androidfw/tests/data/out_of_order_types/build @@ -1,4 +1,6 @@ -# Copyright (C) 2010 The Android Open Source Project +#!/bin/bash +# +# Copyright (C) 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,14 +13,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# -LOCAL_PATH:= $(call my-dir) - -# Include subdirectory makefiles -# ============================================================ +set -e -# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework -# team really wants is to build the stuff defined by this makefile. -ifeq (,$(ONE_SHOT_MAKEFILE)) -include $(call first-makefiles-under,$(LOCAL_PATH)) -endif +aapt2 compile --dir res -o compiled.flata +aapt2 link --manifest AndroidManifest.xml -o out_of_order_types.apk compiled.flata +rm compiled.flata diff --git a/libs/androidfw/tests/data/out_of_order_types/edited_resources.arsc.txt b/libs/androidfw/tests/data/out_of_order_types/edited_resources.arsc.txt new file mode 100644 index 000000000000..eca8f478c501 --- /dev/null +++ b/libs/androidfw/tests/data/out_of_order_types/edited_resources.arsc.txt @@ -0,0 +1,43 @@ +00000000: 0200 0c00 ac02 0000 0100 0000 0100 1c00 ................ +00000010: 1c00 0000 0000 0000 0000 0000 0001 0000 ................ +00000020: 1c00 0000 0000 0000 0002 2001 8402 0000 .......... ..... +00000030: 7f00 0000 6300 6f00 6d00 2e00 6100 6e00 ....c.o.m...a.n. +00000040: 6400 7200 6f00 6900 6400 2e00 6100 7000 d.r.o.i.d...a.p. +00000050: 7000 0000 0000 0000 0000 0000 0000 0000 p............... +00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000120: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000130: 0000 0000 2001 0000 0000 0000 6401 0000 .... .......d... +00000140: 0000 0000 0000 0000 0100 1c00 4400 0000 ............D... +00000150: 0200 0000 0000 0000 0000 0000 2400 0000 ............$... +00000160: 0000 0000 0000 0000 0c00 0000 0400 6200 ..............b. +00000170: 6f00 6f00 6c00 0000 0700 6900 6e00 7400 o.o.l.....i.n.t. +00000180: 6500 6700 6500 7200 0000 0000 0100 1c00 e.g.e.r......... +00000190: 2800 0000 0100 0000 0000 0000 0001 0000 (............... +000001a0: 2000 0000 0000 0000 0000 0000 0404 7465 .............te +000001b0: 7374 0000 0202 1000 1400 0000 0100 0000 st.............. +000001c0: 0100 0000 0000 0000 0202 1000 1400 0000 +000001d0: 0200 0000 0100 0000 0000 0000 0102 5400 +000001e0: 6800 0000 0100 0000 0100 0000 5800 0000 +000001f0: 4000 0000 0000 0000 0000 0000 0000 0000 +00000200: 0000 0000 0000 0000 0000 0000 0000 0000 +00000210: 0000 0000 0000 0000 0000 0000 0000 0000 +00000220: 0000 0000 0000 0000 0000 0000 0000 0000 +00000230: 0000 0000 0800 0000 0000 0000 0800 0012 +00000240: ffff ffff 0102 5400 6800 0000 0200 0000 ......T.h....... +00000250: 0100 0000 5800 0000 4000 0000 0000 0000 ....X...@....... +00000260: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000270: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000280: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000290: 0000 0000 0000 0000 0000 0000 0800 0000 ................ +000002a0: 0000 0000 0800 0010 0100 0000 ............ diff --git a/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk b/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk Binary files differnew file mode 100644 index 000000000000..75146e0fc476 --- /dev/null +++ b/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk diff --git a/libs/androidfw/tests/data/out_of_order_types/res/values/values.xml b/libs/androidfw/tests/data/out_of_order_types/res/values/values.xml new file mode 100644 index 000000000000..7c54fbae9f21 --- /dev/null +++ b/libs/androidfw/tests/data/out_of_order_types/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <bool name="test">true</bool> + <integer name="test">1</integer> +</resources> diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build index 112f373ead30..716b1bd9db64 100755 --- a/libs/androidfw/tests/data/overlay/build +++ b/libs/androidfw/tests/data/overlay/build @@ -17,4 +17,6 @@ set -e -aapt package -M AndroidManifest.xml -S res -F overlay.apk -f +aapt2 compile --dir res -o compiled.flata +aapt2 link --manifest AndroidManifest.xml -o overlay.apk compiled.flata +rm compiled.flata diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk Binary files differindex 40bf17c5951a..33f961117c44 100644 --- a/libs/androidfw/tests/data/overlay/overlay.apk +++ b/libs/androidfw/tests/data/overlay/overlay.apk diff --git a/libs/androidfw/tests/data/styles/R.h b/libs/androidfw/tests/data/styles/R.h index 05073a8869ec..538a84717176 100644 --- a/libs/androidfw/tests/data/styles/R.h +++ b/libs/androidfw/tests/data/styles/R.h @@ -48,6 +48,9 @@ struct R { StyleOne = 0x7f020000u, StyleTwo = 0x7f020001u, StyleThree = 0x7f020002u, + StyleFour = 0x7f020003u, + StyleFive = 0x7f020004u, + StyleSix = 0x7f020005u, }; }; }; diff --git a/libs/androidfw/tests/data/styles/res/values/styles.xml b/libs/androidfw/tests/data/styles/res/values/styles.xml index 3c90317a62c6..1a231768dade 100644 --- a/libs/androidfw/tests/data/styles/res/values/styles.xml +++ b/libs/androidfw/tests/data/styles/res/values/styles.xml @@ -63,4 +63,20 @@ <item name="attr_five">5</item> </style> + <!-- Circular parental dependency --> + <public type="style" name="StyleFour" id="0x7f020003" /> + <style name="StyleFour" parent="StyleFive"> + <item name="attr_one">1</item> + </style> + + <public type="style" name="StyleFive" id="0x7f020004" /> + <style name="StyleFive" parent="StyleSix"> + <item name="attr_two">2</item> + </style> + + <public type="style" name="StyleSix" id="0x7f020005" /> + <style name="StyleSix" parent="StyleFour"> + <item name="attr_three">3</item> + </style> + </resources> diff --git a/libs/androidfw/tests/data/styles/styles.apk b/libs/androidfw/tests/data/styles/styles.apk Binary files differindex 72abf8ff2b40..cd5c7a1c6c12 100644 --- a/libs/androidfw/tests/data/styles/styles.apk +++ b/libs/androidfw/tests/data/styles/styles.apk diff --git a/libs/androidfw/tests/data/system/assets/file.txt b/libs/androidfw/tests/data/system/assets/file.txt new file mode 100644 index 000000000000..f73f3093ff86 --- /dev/null +++ b/libs/androidfw/tests/data/system/assets/file.txt @@ -0,0 +1 @@ +file diff --git a/libs/androidfw/tests/data/system/assets/subdir/subdir_file.txt b/libs/androidfw/tests/data/system/assets/subdir/subdir_file.txt new file mode 100644 index 000000000000..3f74eb6e6441 --- /dev/null +++ b/libs/androidfw/tests/data/system/assets/subdir/subdir_file.txt @@ -0,0 +1 @@ +subdir file diff --git a/libs/androidfw/tests/data/system/build b/libs/androidfw/tests/data/system/build index bfbdf4ca770b..b65145a8454f 100755 --- a/libs/androidfw/tests/data/system/build +++ b/libs/androidfw/tests/data/system/build @@ -17,4 +17,6 @@ set -e -aapt package -x -M AndroidManifest.xml -S res -F system.apk -f +aapt2 compile --dir res -o compiled.flata +aapt2 link --manifest AndroidManifest.xml -A assets -o system.apk compiled.flata +rm compiled.flata diff --git a/libs/androidfw/tests/data/system/res/values-sv/values.xml b/libs/androidfw/tests/data/system/res/values-sv/values.xml index b97bdb68aca7..5f60d214c744 100644 --- a/libs/androidfw/tests/data/system/res/values-sv/values.xml +++ b/libs/androidfw/tests/data/system/res/values-sv/values.xml @@ -15,6 +15,5 @@ --> <resources> - <public type="integer" name="number" id="0x01030000" /> <integer name="number">1</integer> </resources> diff --git a/libs/androidfw/tests/data/system/res/values/themes.xml b/libs/androidfw/tests/data/system/res/values/themes.xml index 35d43c77fc7a..7893c946e299 100644 --- a/libs/androidfw/tests/data/system/res/values/themes.xml +++ b/libs/androidfw/tests/data/system/res/values/themes.xml @@ -18,6 +18,7 @@ <public name="background" type="attr" id="0x01010000"/> <public name="foreground" type="attr" id="0x01010001"/> <public name="Theme.One" type="style" id="0x01020000"/> + <public type="integer" name="number" id="0x01030000" /> <attr name="background" format="color|reference"/> <attr name="foreground" format="color|reference"/> diff --git a/libs/androidfw/tests/data/system/system.apk b/libs/androidfw/tests/data/system/system.apk Binary files differindex 1299016a0f83..9045d6c4de21 100644 --- a/libs/androidfw/tests/data/system/system.apk +++ b/libs/androidfw/tests/data/system/system.apk |