summaryrefslogtreecommitdiff
path: root/libs/androidfw
diff options
context:
space:
mode:
Diffstat (limited to 'libs/androidfw')
-rw-r--r--libs/androidfw/Android.bp105
-rw-r--r--libs/androidfw/ApkAssets.cpp134
-rw-r--r--libs/androidfw/AssetManager.cpp116
-rw-r--r--libs/androidfw/AssetManager2.cpp702
-rw-r--r--libs/androidfw/AttributeResolution.cpp263
-rw-r--r--libs/androidfw/Idmap.cpp190
-rw-r--r--libs/androidfw/LoadedArsc.cpp577
-rw-r--r--libs/androidfw/ResourceTypes.cpp927
-rw-r--r--libs/androidfw/ResourceUtils.cpp3
-rw-r--r--libs/androidfw/ZipFileRO.cpp24
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h56
-rw-r--r--libs/androidfw/include/androidfw/AssetManager.h34
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h176
-rw-r--r--libs/androidfw/include/androidfw/AttributeFinder.h6
-rw-r--r--libs/androidfw/include/androidfw/AttributeResolution.h11
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h76
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h167
-rw-r--r--libs/androidfw/include/androidfw/MutexGuard.h101
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h94
-rw-r--r--libs/androidfw/include/androidfw/ResourceUtils.h6
-rw-r--r--libs/androidfw/include/androidfw/ZipFileRO.h6
-rw-r--r--libs/androidfw/tests/Android.mk126
-rw-r--r--libs/androidfw/tests/ApkAssets_test.cpp102
-rw-r--r--libs/androidfw/tests/AssetManager2_bench.cpp91
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp159
-rw-r--r--libs/androidfw/tests/AttributeResolution_bench.cpp175
-rw-r--r--libs/androidfw/tests/AttributeResolution_test.cpp75
-rw-r--r--libs/androidfw/tests/BenchMain.cpp2
-rw-r--r--libs/androidfw/tests/BenchmarkHelpers.cpp41
-rw-r--r--libs/androidfw/tests/BenchmarkHelpers.h14
-rw-r--r--libs/androidfw/tests/CommonHelpers.cpp65
-rw-r--r--libs/androidfw/tests/CommonHelpers.h57
-rw-r--r--libs/androidfw/tests/ConfigLocale_test.cpp95
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp2
-rw-r--r--libs/androidfw/tests/LoadedArsc_test.cpp251
-rw-r--r--libs/androidfw/tests/ResTable_test.cpp52
-rw-r--r--libs/androidfw/tests/ResourceUtils_test.cpp25
-rw-r--r--libs/androidfw/tests/SparseEntry_bench.cpp33
-rw-r--r--libs/androidfw/tests/TestHelpers.cpp90
-rw-r--r--libs/androidfw/tests/TestHelpers.h37
-rw-r--r--libs/androidfw/tests/Theme_test.cpp35
-rw-r--r--libs/androidfw/tests/data/app/app.apkbin1138 -> 1402 bytes
-rw-r--r--libs/androidfw/tests/data/app/assets/app_file.txt1
-rw-r--r--libs/androidfw/tests/data/app/assets/file.txt1
-rwxr-xr-xlibs/androidfw/tests/data/app/build9
-rw-r--r--libs/androidfw/tests/data/basic/R.h2
-rw-r--r--libs/androidfw/tests/data/basic/basic.apkbin3207 -> 5448 bytes
-rw-r--r--libs/androidfw/tests/data/basic/basic_de_fr.apkbin1524 -> 1327 bytes
-rw-r--r--libs/androidfw/tests/data/basic/basic_hdpi-v4.apkbin1306 -> 1151 bytes
-rw-r--r--libs/androidfw/tests/data/basic/basic_xhdpi-v4.apkbin1312 -> 1155 bytes
-rw-r--r--libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apkbin1310 -> 1155 bytes
-rwxr-xr-xlibs/androidfw/tests/data/basic/build18
-rw-r--r--libs/androidfw/tests/data/basic/res/layout/layout.xml25
-rw-r--r--libs/androidfw/tests/data/basic/res/values/values.xml22
-rw-r--r--libs/androidfw/tests/data/length_decode/length_decode_invalid.apkbin0 -> 41207 bytes
-rw-r--r--libs/androidfw/tests/data/length_decode/length_decode_valid.apkbin0 -> 41207 bytes
-rw-r--r--libs/androidfw/tests/data/out_of_order_types/AndroidManifest.xml18
-rwxr-xr-x[-rw-r--r--]libs/androidfw/tests/data/out_of_order_types/build (renamed from libs/androidfw/Android.mk)18
-rw-r--r--libs/androidfw/tests/data/out_of_order_types/edited_resources.arsc.txt43
-rw-r--r--libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apkbin0 -> 1151 bytes
-rw-r--r--libs/androidfw/tests/data/out_of_order_types/res/values/values.xml20
-rwxr-xr-xlibs/androidfw/tests/data/overlay/build4
-rw-r--r--libs/androidfw/tests/data/overlay/overlay.apkbin2442 -> 5211 bytes
-rw-r--r--libs/androidfw/tests/data/styles/R.h3
-rw-r--r--libs/androidfw/tests/data/styles/res/values/styles.xml16
-rw-r--r--libs/androidfw/tests/data/styles/styles.apkbin2350 -> 2550 bytes
-rw-r--r--libs/androidfw/tests/data/system/assets/file.txt1
-rw-r--r--libs/androidfw/tests/data/system/assets/subdir/subdir_file.txt1
-rwxr-xr-xlibs/androidfw/tests/data/system/build4
-rw-r--r--libs/androidfw/tests/data/system/res/values-sv/values.xml1
-rw-r--r--libs/androidfw/tests/data/system/res/values/themes.xml1
-rw-r--r--libs/androidfw/tests/data/system/system.apkbin1417 -> 1624 bytes
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, &current_entry,
- &current_config, &current_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
index ccb08242a656..c8ad86ded851 100644
--- a/libs/androidfw/tests/data/app/app.apk
+++ b/libs/androidfw/tests/data/app/app.apk
Binary files differ
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
index 0c17328e86b2..b721ebfde445 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk
index e45258c6a005..767dff6fcfa5 100644
--- a/libs/androidfw/tests/data/basic/basic_de_fr.apk
+++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
index 4ae1a7c87a70..58953f56d736 100644
--- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
index a240d4c06c1d..103f6565bb06 100644
--- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
index fd3d9b233084..61369d506786 100644
--- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
Binary files differ
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
new file mode 100644
index 000000000000..b089651e6786
--- /dev/null
+++ b/libs/androidfw/tests/data/length_decode/length_decode_invalid.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/length_decode/length_decode_valid.apk b/libs/androidfw/tests/data/length_decode/length_decode_valid.apk
new file mode 100644
index 000000000000..add23e1ee6d4
--- /dev/null
+++ b/libs/androidfw/tests/data/length_decode/length_decode_valid.apk
Binary files differ
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
new file mode 100644
index 000000000000..75146e0fc476
--- /dev/null
+++ b/libs/androidfw/tests/data/out_of_order_types/out_of_order_types.apk
Binary files differ
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
index 40bf17c5951a..33f961117c44 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
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
index 72abf8ff2b40..cd5c7a1c6c12 100644
--- a/libs/androidfw/tests/data/styles/styles.apk
+++ b/libs/androidfw/tests/data/styles/styles.apk
Binary files differ
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
index 1299016a0f83..9045d6c4de21 100644
--- a/libs/androidfw/tests/data/system/system.apk
+++ b/libs/androidfw/tests/data/system/system.apk
Binary files differ