summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author MÃ¥rten Kongstad <marten.kongstad@sonyericsson.com> 2011-03-17 14:13:41 +0100
committer Kenneth Andersson <kenneth.andersson@sonyericsson.com> 2011-04-01 14:12:10 +0200
commit57f4b77c89bafedf9468f9a636561c0c193405c9 (patch)
tree9e73065411411858c9c6ae350c5ee8ffe14598c3
parentc6b22e2727bd3730edd5ad72831a9bb4670d00f3 (diff)
Runtime resource overlay, iteration 1.
Runtime resource overlay allows unmodified applications to appear as if they had been compiled with additional resources defined. See libs/utils/README for more information. This commit is the first iteration of runtime resource overlay. It provides the actual overlay modifications and loading of trusted overlay packages (ie residing in /vendor) targeting framework-res.apk. This commit loads exactly one overlay package. The overlay, if present, must target framework-res.apk and be located at /vendor/overlay/framework/framework-res.apk. Change-Id: If26ee7754813004a96c043dba37fbe99fa3919db
-rw-r--r--core/tests/overlaytests/Android.mk4
-rw-r--r--core/tests/overlaytests/OverlayTest/Android.mk10
-rw-r--r--core/tests/overlaytests/OverlayTest/AndroidManifest.xml10
-rw-r--r--core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java118
-rw-r--r--core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java7
-rw-r--r--core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java7
-rw-r--r--core/tests/overlaytests/OverlayTestOverlay/Android.mk14
-rw-r--r--core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml6
-rw-r--r--core/tests/overlaytests/OverlayTestOverlay/res/drawable/default_wallpaper.jpgbin0 -> 399 bytes
-rw-r--r--core/tests/overlaytests/OverlayTestOverlay/res/values-sv/config.xml4
-rw-r--r--core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml13
-rw-r--r--core/tests/overlaytests/README15
-rwxr-xr-xcore/tests/overlaytests/runtests.sh89
-rw-r--r--include/utils/AssetManager.h11
-rw-r--r--include/utils/ResourceTypes.h26
-rw-r--r--libs/utils/AssetManager.cpp237
-rw-r--r--libs/utils/README275
-rw-r--r--libs/utils/ResourceTypes.cpp404
-rw-r--r--tools/aapt/Bundle.h4
-rw-r--r--tools/aapt/Main.cpp5
-rw-r--r--tools/aapt/ResourceTable.cpp4
21 files changed, 1202 insertions, 61 deletions
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
new file mode 100644
index 000000000000..bf694426cbce
--- /dev/null
+++ b/core/tests/overlaytests/Android.mk
@@ -0,0 +1,4 @@
+# Dummy makefile to halt recursive directory traversal.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
diff --git a/core/tests/overlaytests/OverlayTest/Android.mk b/core/tests/overlaytests/OverlayTest/Android.mk
new file mode 100644
index 000000000000..f7f67f67624f
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTest/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PACKAGE_NAME := OverlayTest
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/overlaytests/OverlayTest/AndroidManifest.xml b/core/tests/overlaytests/OverlayTest/AndroidManifest.xml
new file mode 100644
index 000000000000..9edba12ffa8f
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTest/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest">
+ <uses-permission android:name="android.permission.RUN_INSTRUMENTATION"/>
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.overlaytest"
+ android:label="Runtime resource overlay tests"/>
+</manifest>
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
new file mode 100644
index 000000000000..85b49ce5c9b8
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
@@ -0,0 +1,118 @@
+package com.android.overlaytest;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import java.io.InputStream;
+import java.util.Locale;
+
+public abstract class OverlayBaseTest extends AndroidTestCase {
+ private Resources mResources;
+ protected boolean mWithOverlay; // will be set by subclasses
+
+ protected void setUp() {
+ mResources = getContext().getResources();
+ }
+
+ private int calculateRawResourceChecksum(int resId) throws Throwable {
+ InputStream input = null;
+ try {
+ input = mResources.openRawResource(resId);
+ int ch, checksum = 0;
+ while ((ch = input.read()) != -1) {
+ checksum = (checksum + ch) % 0xffddbb00;
+ }
+ return checksum;
+ } finally {
+ input.close();
+ }
+ }
+
+ private void setLocale(String code) {
+ Locale locale = new Locale(code);
+ Locale.setDefault(locale);
+ Configuration config = new Configuration();
+ config.locale = locale;
+ mResources.updateConfiguration(config, mResources.getDisplayMetrics());
+ }
+
+ private void assertResource(int resId, boolean ewo, boolean ew) throws Throwable {
+ boolean expected = mWithOverlay ? ew : ewo;
+ boolean actual = mResources.getBoolean(resId);
+ assertEquals(expected, actual);
+ }
+
+ private void assertResource(int resId, String ewo, String ew) throws Throwable {
+ String expected = mWithOverlay ? ew : ewo;
+ String actual = mResources.getString(resId);
+ assertEquals(expected, actual);
+ }
+
+ private void assertResource(int resId, int[] ewo, int[] ew) throws Throwable {
+ int[] expected = mWithOverlay ? ew : ewo;
+ int[] actual = mResources.getIntArray(resId);
+ assertEquals("length:", expected.length, actual.length);
+ for (int i = 0; i < actual.length; ++i) {
+ assertEquals("index " + i + ":", actual[i], expected[i]);
+ }
+ }
+
+ public void testBooleanOverlay() throws Throwable {
+ // config_automatic_brightness_available has overlay (default config)
+ final int resId = com.android.internal.R.bool.config_automatic_brightness_available;
+ assertResource(resId, false, true);
+ }
+
+ public void testBoolean() throws Throwable {
+ // config_bypass_keyguard_if_slider_open has no overlay
+ final int resId = com.android.internal.R.bool.config_bypass_keyguard_if_slider_open;
+ assertResource(resId, true, true);
+ }
+
+ public void testStringOverlay() throws Throwable {
+ // phoneTypeCar has an overlay (default config), which shouldn't shadow
+ // the Swedish translation
+ final int resId = com.android.internal.R.string.phoneTypeCar;
+ setLocale("sv_SE");
+ assertResource(resId, "Bil", "Bil");
+ }
+
+ public void testStringSwedishOverlay() throws Throwable {
+ // phoneTypeWork has overlay (no default config, only for lang=sv)
+ final int resId = com.android.internal.R.string.phoneTypeWork;
+ setLocale("en_US");
+ assertResource(resId, "Work", "Work");
+ setLocale("sv_SE");
+ assertResource(resId, "Arbete", "Jobb");
+ }
+
+ public void testString() throws Throwable {
+ // phoneTypeHome has no overlay
+ final int resId = com.android.internal.R.string.phoneTypeHome;
+ setLocale("en_US");
+ assertResource(resId, "Home", "Home");
+ setLocale("sv_SE");
+ assertResource(resId, "Hem", "Hem");
+ }
+
+ public void testIntegerArrayOverlay() throws Throwable {
+ // config_scrollBarrierVibePattern has overlay (default config)
+ final int resId = com.android.internal.R.array.config_scrollBarrierVibePattern;
+ assertResource(resId, new int[]{0, 15, 10, 10}, new int[]{100, 200, 300});
+ }
+
+ public void testIntegerArray() throws Throwable {
+ // config_virtualKeyVibePattern has no overlay
+ final int resId = com.android.internal.R.array.config_virtualKeyVibePattern;
+ final int[] expected = {0, 10, 20, 30};
+ assertResource(resId, expected, expected);
+ }
+
+ public void testAsset() throws Throwable {
+ // drawable/default_background.jpg has overlay (default config)
+ final int resId = com.android.internal.R.drawable.default_wallpaper;
+ int actual = calculateRawResourceChecksum(resId);
+ int expected = mWithOverlay ? 0x000051da : 0x0014ebce;
+ assertEquals(expected, actual);
+ }
+}
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
new file mode 100644
index 000000000000..1292d03394ac
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
@@ -0,0 +1,7 @@
+package com.android.overlaytest;
+
+public class WithOverlayTest extends OverlayBaseTest {
+ public WithOverlayTest() {
+ mWithOverlay = true;
+ }
+}
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
new file mode 100644
index 000000000000..630ff8fa83bb
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
@@ -0,0 +1,7 @@
+package com.android.overlaytest;
+
+public class WithoutOverlayTest extends OverlayBaseTest {
+ public WithoutOverlayTest() {
+ mWithOverlay = false;
+ }
+}
diff --git a/core/tests/overlaytests/OverlayTestOverlay/Android.mk b/core/tests/overlaytests/OverlayTestOverlay/Android.mk
new file mode 100644
index 000000000000..cf32c9f93c68
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTestOverlay/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := com.android.overlaytest.overlay
+
+LOCAL_AAPT_FLAGS := -o
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml b/core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml
new file mode 100644
index 000000000000..bcbb0d15fe10
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.overlay"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay-package android:name="android"/>
+</manifest>
diff --git a/core/tests/overlaytests/OverlayTestOverlay/res/drawable/default_wallpaper.jpg b/core/tests/overlaytests/OverlayTestOverlay/res/drawable/default_wallpaper.jpg
new file mode 100644
index 000000000000..0d944d02d633
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTestOverlay/res/drawable/default_wallpaper.jpg
Binary files differ
diff --git a/core/tests/overlaytests/OverlayTestOverlay/res/values-sv/config.xml b/core/tests/overlaytests/OverlayTestOverlay/res/values-sv/config.xml
new file mode 100644
index 000000000000..bc5236738bf7
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTestOverlay/res/values-sv/config.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="phoneTypeWork">Jobb</string>
+</resources>
diff --git a/core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml b/core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml
new file mode 100644
index 000000000000..794f475a7926
--- /dev/null
+++ b/core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="config_automatic_brightness_available">true</bool>
+ <string name="phoneTypeCar">Automobile</string>
+ <integer-array name="config_scrollBarrierVibePattern">
+ <item>100</item>
+ <item>200</item>
+ <item>300</item>
+ </integer-array>
+ <!-- The following integer does not exist in the original package. Idmap
+ generation should therefore ignore it. -->
+ <integer name="integer_not_in_original_package">0</integer>
+</resources>
diff --git a/core/tests/overlaytests/README b/core/tests/overlaytests/README
new file mode 100644
index 000000000000..4b3e6f235657
--- /dev/null
+++ b/core/tests/overlaytests/README
@@ -0,0 +1,15 @@
+Unit tests for runtime resource overlay
+=======================================
+
+As of this writing, runtime resource overlay is only triggered for
+/system/framework/framework-res.apk. Because of this, installation of
+overlay packages require the Android platform be rebooted. However, the
+regular unit tests (triggered via development/testrunner/runtest.py)
+cannot handle reboots. As a workaround, this directory contains a shell
+script which will trigger the tests in a non-standard way.
+
+Once runtime resource overlay may be applied to applications, the tests
+in this directory should be moved to core/tests/coretests. Also, by
+applying runtime resource overlay to a dedicated test application, the
+test cases would not need to assume default values for non-overlaid
+resources.
diff --git a/core/tests/overlaytests/runtests.sh b/core/tests/overlaytests/runtests.sh
new file mode 100755
index 000000000000..0ad9efb0938e
--- /dev/null
+++ b/core/tests/overlaytests/runtests.sh
@@ -0,0 +1,89 @@
+#!/bin/bash
+
+adb="adb"
+if [[ $# -gt 0 ]]; then
+ adb="adb $*" # for setting -e, -d or -s <serial>
+fi
+
+function atexit()
+{
+ local retval=$?
+
+ if [[ $retval -eq 0 ]]; then
+ rm $log
+ else
+ echo "There were errors, please check log at $log"
+ fi
+}
+
+log=$(mktemp)
+trap "atexit" EXIT
+failures=0
+
+function compile_module()
+{
+ local android_mk="$1"
+
+ echo "Compiling .${android_mk:${#PWD}}"
+ ONE_SHOT_MAKEFILE="$android_mk" make -C "../../../../../" files | tee -a $log
+ if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
+ exit 1
+ fi
+}
+
+function wait_for_boot_completed()
+{
+ echo "Rebooting device"
+ $adb wait-for-device logcat -c
+ $adb wait-for-device logcat | grep -m 1 -e 'PowerManagerService.*bootCompleted' >/dev/null
+}
+
+function disable_overlay()
+{
+ echo "Disabling overlay"
+ $adb shell rm /vendor/overlay/framework/framework-res.apk
+ $adb shell rm /data/resource-cache/vendor@overlay@framework@framework-res.apk@idmap
+}
+
+function enable_overlay()
+{
+ echo "Enabling overlay"
+ $adb shell ln -s /data/app/com.android.overlaytest.overlay.apk /vendor/overlay/framework/framework-res.apk
+}
+
+function instrument()
+{
+ local class="$1"
+
+ echo "Instrumenting $class"
+ $adb shell am instrument -w -e class $class com.android.overlaytest/android.test.InstrumentationTestRunner | tee -a $log
+}
+
+function sync()
+{
+ echo "Syncing to device"
+ $adb remount | tee -a $log
+ $adb sync data | tee -a $log
+}
+
+# build and sync
+compile_module "$PWD/OverlayTest/Android.mk"
+compile_module "$PWD/OverlayTestOverlay/Android.mk"
+sync
+
+# instrument test (without overlay)
+$adb shell stop
+disable_overlay
+$adb shell start
+wait_for_boot_completed
+instrument "com.android.overlaytest.WithoutOverlayTest"
+
+# instrument test (with overlay)
+$adb shell stop
+enable_overlay
+$adb shell start
+wait_for_boot_completed
+instrument "com.android.overlaytest.WithOverlayTest"
+
+# cleanup
+exit $(grep -c -e '^FAILURES' $log)
diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h
index 9e2bf37e8fa4..a8c7ddbd78a6 100644
--- a/include/utils/AssetManager.h
+++ b/include/utils/AssetManager.h
@@ -222,6 +222,7 @@ private:
{
String8 path;
FileType type;
+ String8 idmap;
};
Asset* openInPathLocked(const char* fileName, AccessMode mode,
@@ -262,6 +263,16 @@ private:
void setLocaleLocked(const char* locale);
void updateResourceParamsLocked() const;
+ bool createIdmapFileLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath);
+
+ bool isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath);
+
+ Asset* openIdmapLocked(const struct asset_path& ap) const;
+
+ bool getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename, uint32_t* pCrc);
+
class SharedZip : public RefBase {
public:
static sp<SharedZip> get(const String8& path);
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index ab7b973a4d8c..10baa11a5114 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -1735,9 +1735,9 @@ public:
~ResTable();
status_t add(const void* data, size_t size, void* cookie,
- bool copyData=false);
+ bool copyData=false, const void* idmap = NULL);
status_t add(Asset* asset, void* cookie,
- bool copyData=false);
+ bool copyData=false, const void* idmap = NULL);
status_t add(ResTable* src);
status_t getError() const;
@@ -1981,6 +1981,24 @@ public:
void getLocales(Vector<String8>* locales) const;
+ // Generate an idmap.
+ //
+ // Return value: on success: NO_ERROR; caller is responsible for free-ing
+ // outData (using free(3)). On failure, any status_t value other than
+ // NO_ERROR; the caller should not free outData.
+ status_t createIdmap(const ResTable& overlay, uint32_t originalCrc, uint32_t overlayCrc,
+ void** outData, size_t* outSize) const;
+
+ enum {
+ IDMAP_HEADER_SIZE_BYTES = 3 * sizeof(uint32_t),
+ };
+ // Retrieve idmap meta-data.
+ //
+ // This function only requires the idmap header (the first
+ // IDMAP_HEADER_SIZE_BYTES) bytes of an idmap file.
+ static bool getIdmapInfo(const void* idmap, size_t size,
+ uint32_t* pOriginalCrc, uint32_t* pOverlayCrc);
+
#ifndef HAVE_ANDROID_OS
void print(bool inclValues) const;
static String8 normalizeForOutput(const char* input);
@@ -1994,7 +2012,7 @@ private:
struct bag_set;
status_t add(const void* data, size_t size, void* cookie,
- Asset* asset, bool copyData);
+ Asset* asset, bool copyData, const Asset* idmap);
ssize_t getResourcePackageIndex(uint32_t resID) const;
ssize_t getEntry(
@@ -2003,7 +2021,7 @@ private:
const ResTable_type** outType, const ResTable_entry** outEntry,
const Type** outTypeClass) const;
status_t parsePackage(
- const ResTable_package* const pkg, const Header* const header);
+ const ResTable_package* const pkg, const Header* const header, uint32_t idmap_id);
void print_value(const Package* pkg, const Res_value& value) const;
diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp
index e09e755861dc..13004cdf7332 100644
--- a/libs/utils/AssetManager.cpp
+++ b/libs/utils/AssetManager.cpp
@@ -36,6 +36,19 @@
#include <dirent.h>
#include <errno.h>
#include <assert.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
using namespace android;
@@ -48,6 +61,7 @@ static const char* kDefaultVendor = "default";
static const char* kAssetsRoot = "assets";
static const char* kAppZipName = NULL; //"classes.jar";
static const char* kSystemAssets = "framework/framework-res.apk";
+static const char* kIdmapCacheDir = "resource-cache";
static const char* kExcludeExtension = ".EXCLUDE";
@@ -55,6 +69,35 @@ static Asset* const kExcludedAsset = (Asset*) 0xd000000d;
static volatile int32_t gCount = 0;
+namespace {
+ // Transform string /a/b/c.apk to /data/resource-cache/a@b@c.apk@idmap
+ String8 idmapPathForPackagePath(const String8& pkgPath)
+ {
+ const char* root = getenv("ANDROID_DATA");
+ LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
+ String8 path(root);
+ path.appendPath(kIdmapCacheDir);
+
+ char buf[256]; // 256 chars should be enough for anyone...
+ strncpy(buf, pkgPath.string(), 255);
+ buf[255] = '\0';
+ char* filename = buf;
+ while (*filename && *filename == '/') {
+ ++filename;
+ }
+ char* p = filename;
+ while (*p) {
+ if (*p == '/') {
+ *p = '@';
+ }
+ ++p;
+ }
+ path.appendPath(filename);
+ path.append("@idmap");
+
+ return path;
+ }
+}
/*
* ===========================================================================
@@ -122,7 +165,7 @@ bool AssetManager::addAssetPath(const String8& path, void** cookie)
return true;
}
}
-
+
LOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
@@ -133,9 +176,181 @@ bool AssetManager::addAssetPath(const String8& path, void** cookie)
*cookie = (void*)mAssetPaths.size();
}
+ // add overlay packages for /system/framework; apps are handled by the
+ // (Java) package manager
+ if (strncmp(path.string(), "/system/framework/", 18) == 0) {
+ // When there is an environment variable for /vendor, this
+ // should be changed to something similar to how ANDROID_ROOT
+ // and ANDROID_DATA are used in this file.
+ String8 overlayPath("/vendor/overlay/framework/");
+ overlayPath.append(path.getPathLeaf());
+ if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {
+ asset_path oap;
+ oap.path = overlayPath;
+ oap.type = ::getFileType(overlayPath.string());
+ bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
+ if (addOverlay) {
+ oap.idmap = idmapPathForPackagePath(overlayPath);
+
+ if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
+ addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
+ }
+ }
+ if (addOverlay) {
+ mAssetPaths.add(oap);
+ } else {
+ LOGW("failed to add overlay package %s\n", overlayPath.string());
+ }
+ }
+ }
+
+ return true;
+}
+
+bool AssetManager::isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath)
+{
+ struct stat st;
+ if (TEMP_FAILURE_RETRY(stat(idmapPath.string(), &st)) == -1) {
+ if (errno == ENOENT) {
+ return true; // non-existing idmap is always stale
+ } else {
+ LOGW("failed to stat file %s: %s\n", idmapPath.string(), strerror(errno));
+ return false;
+ }
+ }
+ if (st.st_size < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+ LOGW("file %s has unexpectedly small size=%zd\n", idmapPath.string(), (size_t)st.st_size);
+ return false;
+ }
+ int fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_RDONLY));
+ if (fd == -1) {
+ LOGW("failed to open file %s: %s\n", idmapPath.string(), strerror(errno));
+ return false;
+ }
+ char buf[ResTable::IDMAP_HEADER_SIZE_BYTES];
+ ssize_t bytesLeft = ResTable::IDMAP_HEADER_SIZE_BYTES;
+ for (;;) {
+ ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf + ResTable::IDMAP_HEADER_SIZE_BYTES - bytesLeft,
+ bytesLeft));
+ if (r < 0) {
+ TEMP_FAILURE_RETRY(close(fd));
+ return false;
+ }
+ bytesLeft -= r;
+ if (bytesLeft == 0) {
+ break;
+ }
+ }
+ TEMP_FAILURE_RETRY(close(fd));
+
+ uint32_t cachedOriginalCrc, cachedOverlayCrc;
+ if (!ResTable::getIdmapInfo(buf, ResTable::IDMAP_HEADER_SIZE_BYTES,
+ &cachedOriginalCrc, &cachedOverlayCrc)) {
+ return false;
+ }
+
+ uint32_t actualOriginalCrc, actualOverlayCrc;
+ if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &actualOriginalCrc)) {
+ return false;
+ }
+ if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &actualOverlayCrc)) {
+ return false;
+ }
+ return cachedOriginalCrc != actualOriginalCrc || cachedOverlayCrc != actualOverlayCrc;
+}
+
+bool AssetManager::getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename,
+ uint32_t* pCrc)
+{
+ asset_path ap;
+ ap.path = zipPath;
+ const ZipFileRO* zip = getZipFileLocked(ap);
+ if (zip == NULL) {
+ return false;
+ }
+ const ZipEntryRO entry = zip->findEntryByName(entryFilename);
+ if (entry == NULL) {
+ return false;
+ }
+ if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)pCrc)) {
+ return false;
+ }
return true;
}
+bool AssetManager::createIdmapFileLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath)
+{
+ LOGD("%s: originalPath=%s overlayPath=%s idmapPath=%s\n",
+ __FUNCTION__, originalPath.string(), overlayPath.string(), idmapPath.string());
+ ResTable tables[2];
+ const String8* paths[2] = { &originalPath, &overlayPath };
+ uint32_t originalCrc, overlayCrc;
+ bool retval = false;
+ ssize_t offset = 0;
+ int fd = 0;
+ uint32_t* data = NULL;
+ size_t size;
+
+ for (int i = 0; i < 2; ++i) {
+ asset_path ap;
+ ap.type = kFileTypeRegular;
+ ap.path = *paths[i];
+ Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
+ if (ass == NULL) {
+ LOGW("failed to find resources.arsc in %s\n", ap.path.string());
+ goto error;
+ }
+ tables[i].add(ass, (void*)1, false);
+ }
+
+ if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &originalCrc)) {
+ LOGW("failed to retrieve crc for resources.arsc in %s\n", originalPath.string());
+ goto error;
+ }
+ if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &overlayCrc)) {
+ LOGW("failed to retrieve crc for resources.arsc in %s\n", overlayPath.string());
+ goto error;
+ }
+
+ if (tables[0].createIdmap(tables[1], originalCrc, overlayCrc,
+ (void**)&data, &size) != NO_ERROR) {
+ LOGW("failed to generate idmap data for file %s\n", idmapPath.string());
+ goto error;
+ }
+
+ // This should be abstracted (eg replaced by a stand-alone
+ // application like dexopt, triggered by something equivalent to
+ // installd).
+ fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_WRONLY | O_CREAT | O_TRUNC, 0644));
+ if (fd == -1) {
+ LOGW("failed to write idmap file %s (open: %s)\n", idmapPath.string(), strerror(errno));
+ goto error_free;
+ }
+ for (;;) {
+ ssize_t written = TEMP_FAILURE_RETRY(write(fd, data + offset, size));
+ if (written < 0) {
+ LOGW("failed to write idmap file %s (write: %s)\n", idmapPath.string(),
+ strerror(errno));
+ goto error_close;
+ }
+ size -= (size_t)written;
+ offset += written;
+ if (size == 0) {
+ break;
+ }
+ }
+
+ retval = true;
+error_close:
+ TEMP_FAILURE_RETRY(close(fd));
+error_free:
+ free(data);
+error:
+ return retval;
+}
+
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
@@ -404,6 +619,7 @@ const ResTable* AssetManager::getResTable(bool required) const
ResTable* sharedRes = NULL;
bool shared = true;
const asset_path& ap = mAssetPaths.itemAt(i);
+ Asset* idmap = openIdmapLocked(ap);
LOGV("Looking for resource asset in '%s'\n", ap.path.string());
if (ap.type != kFileTypeDirectory) {
if (i == 0) {
@@ -433,7 +649,7 @@ const ResTable* AssetManager::getResTable(bool required) const
// can quickly copy it out for others.
LOGV("Creating shared resources for %s", ap.path.string());
sharedRes = new ResTable();
- sharedRes->add(ass, (void*)(i+1), false);
+ sharedRes->add(ass, (void*)(i+1), false, idmap);
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
@@ -457,7 +673,7 @@ const ResTable* AssetManager::getResTable(bool required) const
rt->add(sharedRes);
} else {
LOGV("Parsing resources for %s", ap.path.string());
- rt->add(ass, (void*)(i+1), !shared);
+ rt->add(ass, (void*)(i+1), !shared, idmap);
}
if (!shared) {
@@ -498,6 +714,21 @@ void AssetManager::updateResourceParamsLocked() const
res->setParameters(mConfig);
}
+Asset* AssetManager::openIdmapLocked(const struct asset_path& ap) const
+{
+ Asset* ass = NULL;
+ if (ap.idmap.size() != 0) {
+ ass = const_cast<AssetManager*>(this)->
+ openAssetFromFileLocked(ap.idmap, Asset::ACCESS_BUFFER);
+ if (ass) {
+ LOGV("loading idmap %s\n", ap.idmap.string());
+ } else {
+ LOGW("failed to load idmap %s\n", ap.idmap.string());
+ }
+ }
+ return ass;
+}
+
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
diff --git a/libs/utils/README b/libs/utils/README
index 36a706d5c0db..01741e0930c7 100644
--- a/libs/utils/README
+++ b/libs/utils/README
@@ -1,4 +1,6 @@
Android Utility Function Library
+================================
+
If you need a feature that is native to Linux but not present on other
platforms, construct a platform-dependent implementation that shares
@@ -12,3 +14,276 @@ The ultimate goal is *not* to create a super-duper platform abstraction
layer. The goal is to provide an optimized solution for Linux with
reasonable implementations for other platforms.
+
+
+Resource overlay
+================
+
+
+Introduction
+------------
+
+Overlay packages are special .apk files which provide no code but
+additional resource values (and possibly new configurations) for
+resources in other packages. When an application requests resources,
+the system will return values from either the application's original
+package or any associated overlay package. Any redirection is completely
+transparent to the calling application.
+
+Resource values have the following precedence table, listed in
+descending precedence.
+
+ * overlay package, matching config (eg res/values-en-land)
+
+ * original package, matching config
+
+ * overlay package, no config (eg res/values)
+
+ * original package, no config
+
+During compilation, overlay packages are differentiated from regular
+packages by passing the -o flag to aapt.
+
+
+Background
+----------
+
+This section provides generic background material on resources in
+Android.
+
+
+How resources are bundled in .apk files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Android .apk files are .zip files, usually housing .dex code,
+certificates and resources, though packages containing resources but
+no code are possible. Resources can be divided into the following
+categories; a `configuration' indicates a set of phone language, display
+density, network operator, etc.
+
+ * assets: uncompressed, raw files packaged as part of an .apk and
+ explicitly referenced by filename. These files are
+ independent of configuration.
+
+ * res/drawable: bitmap or xml graphics. Each file may have different
+ values depending on configuration.
+
+ * res/values: integers, strings, etc. Each resource may have different
+ values depending on configuration.
+
+Resource meta information and information proper is stored in a binary
+format in a named file resources.arsc, bundled as part of the .apk.
+
+Resource IDs and lookup
+~~~~~~~~~~~~~~~~~~~~~~~
+During compilation, the aapt tool gathers application resources and
+generates a resources.arsc file. Each resource name is assigned an
+integer ID 0xppttiii (translated to a symbolic name via R.java), where
+
+ * pp: corresponds to the package namespace (details below).
+
+ * tt: corresponds to the resource type (string, int, etc). Every
+ resource of the same type within the same package has the same
+ tt value, but depending on available types, the actual numerical
+ value may be different between packages.
+
+ * iiii: sequential number, assigned in the order resources are found.
+
+Resource values are specified paired with a set of configuration
+constraints (the default being the empty set), eg res/values-sv-port
+which imposes restrictions on language (Swedish) and display orientation
+(portrait). During lookup, every constraint set is matched against the
+current configuration, and the value corresponding to the best matching
+constraint set is returned (ResourceTypes.{h,cpp}).
+
+Parsing of resources.arsc is handled by ResourceTypes.cpp; this utility
+is governed by AssetManager.cpp, which tracks loaded resources per
+process.
+
+Assets are looked up by path and filename in AssetManager.cpp. The path
+to resources in res/drawable are located by ResourceTypes.cpp and then
+handled like assets by AssetManager.cpp. Other resources are handled
+solely by ResourceTypes.cpp.
+
+Package ID as namespace
+~~~~~~~~~~~~~~~~~~~~~~~
+The pp part of a resource ID defines a namespace. Android currently
+defines two namespaces:
+
+ * 0x01: system resources (pre-installed in framework-res.apk)
+
+ * 0x7f: application resources (bundled in the application .apk)
+
+ResourceTypes.cpp supports package IDs between 0x01 and 0x7f
+(inclusive); values outside this range are invalid.
+
+Each running (Dalvik) process is assigned a unique instance of
+AssetManager, which in turn keeps a forest structure of loaded
+resource.arsc files. Normally, this forest is structured as follows,
+where mPackageMap is the internal vector employed in ResourceTypes.cpp.
+
+mPackageMap[0x00] -> system package
+mPackageMap[0x01] -> NULL
+mPackageMap[0x02] -> NULL
+...
+mPackageMap[0x7f - 2] -> NULL
+mPackageMap[0x7f - 1] -> application package
+
+
+
+The resource overlay extension
+------------------------------
+
+The resource overlay mechanism aims to (partly) shadow and extend
+existing resources with new values for defined and new configurations.
+Technically, this is achieved by adding resource-only packages (called
+overlay packages) to existing resource namespaces, like so:
+
+mPackageMap[0x00] -> system package -> system overlay package
+mPackageMap[0x01] -> NULL
+mPackageMap[0x02] -> NULL
+...
+mPackageMap[0x7f - 2] -> NULL
+mPackageMap[0x7f - 1] -> application package -> overlay 1 -> overlay 2
+
+The use of overlay resources is completely transparent to
+applications; no additional resource identifiers are introduced, only
+configuration/value pairs. Any number of overlay packages may be loaded
+at a time; overlay packages are agnostic to what they target -- both
+system and application resources are fair game.
+
+The package targeted by an overlay package is called the target or
+original package.
+
+Resource overlay operates on symbolic resources names. Hence, to
+override the string/str1 resources in a package, the overlay package
+would include a resource also named string/str1. The end user does not
+have to worry about the numeric resources IDs assigned by aapt, as this
+is resolved automatically by the system.
+
+As of this writing, the use of resource overlay has not been fully
+explored. Until it has, only OEMs are trusted to use resource overlay.
+For this reason, overlay packages must reside in /system/overlay.
+
+
+Resource ID mapping
+~~~~~~~~~~~~~~~~~~~
+Resource identifiers must be coherent within the same namespace (ie
+PackageGroup in ResourceTypes.cpp). Calling applications will refer to
+resources using the IDs defined in the original package, but there is no
+guarantee aapt has assigned the same ID to the corresponding resource in
+an overlay package. To translate between the two, a resource ID mapping
+{original ID -> overlay ID} is created during package installation
+(PackageManagerService.java) and used during resource lookup. The
+mapping is stored in /data/resource-cache, with a @idmap file name
+suffix.
+
+The idmap file format is documented in a separate section, below.
+
+
+Package management
+~~~~~~~~~~~~~~~~~~
+Packages are managed by the PackageManagerService. Addition and removal
+of packages are monitored via the inotify framework, exposed via
+android.os.FileObserver.
+
+During initialization of a Dalvik process, ActivityThread.java requests
+the process' AssetManager (by proxy, via AssetManager.java and JNI)
+to load a list of packages. This list includes overlay packages, if
+present.
+
+When a target package or a corresponding overlay package is installed,
+the target package's process is stopped and a new idmap is generated.
+This is similar to how applications are stopped when their packages are
+upgraded.
+
+
+Creating overlay packages
+-------------------------
+
+Overlay packages should contain no code, define (some) resources with
+the same type and name as in the original package, and be compiled with
+the -o flag passed to aapt.
+
+The aapt -o flag instructs aapt to create an overlay package.
+Technically, this means the package will be assigned package id 0x00.
+
+There are no restrictions on overlay packages names, though the naming
+convention <original.package.name>.overlay.<name> is recommended.
+
+
+Example overlay package
+~~~~~~~~~~~~~~~~~~~~~~~
+
+To overlay the resource bool/b in package com.foo.bar, to be applied
+when the display is in landscape mode, create a new package with
+no source code and a single .xml file under res/values-land, with
+an entry for bool/b. Compile with aapt -o and place the results in
+/system/overlay by adding the following to Android.mk:
+
+LOCAL_AAPT_FLAGS := -o com.foo.bar
+LOCAL_MODULE_PATH := $(TARGET_OUT)/overlay
+
+
+The ID map (idmap) file format
+------------------------------
+
+The idmap format is designed for lookup performance. However, leading
+and trailing undefined overlay values are discarded to reduce the memory
+footprint.
+
+
+idmap grammar
+~~~~~~~~~~~~~
+All atoms (names in square brackets) are uint32_t integers. The
+idmap-magic constant spells "idmp" in ASCII. Offsets are given relative
+to the data_header, not to the beginning of the file.
+
+map := header data
+header := idmap-magic <crc32-original-pkg> <crc32-overlay-pkg>
+idmap-magic := <0x706d6469>
+data := data_header type_block+
+data_header := <m> header_block{m}
+header_block := <0> | <type_block_offset>
+type_block := <n> <id_offset> entry{n}
+entry := <resource_id_in_target_package>
+
+
+idmap example
+~~~~~~~~~~~~~
+Given a pair of target and overlay packages with CRC sums 0x216a8fe2
+and 0x6b9beaec, each defining the following resources
+
+Name Target package Overlay package
+string/str0 0x7f010000 -
+string/str1 0x7f010001 0x7f010000
+string/str2 0x7f010002 -
+string/str3 0x7f010003 0x7f010001
+string/str4 0x7f010004 -
+bool/bool0 0x7f020000 -
+integer/int0 0x7f030000 0x7f020000
+integer/int1 0x7f030001 -
+
+the corresponding resource map is
+
+0x706d6469 0x216a8fe2 0x6b9beaec 0x00000003 \
+0x00000004 0x00000000 0x00000009 0x00000003 \
+0x00000001 0x7f010000 0x00000000 0x7f010001 \
+0x00000001 0x00000000 0x7f020000
+
+or, formatted differently
+
+0x706d6469 # magic: all idmap files begin with this constant
+0x216a8fe2 # CRC32 of the resources.arsc file in the original package
+0x6b9beaec # CRC32 of the resources.arsc file in the overlay package
+0x00000003 # header; three types (string, bool, integer) in the target package
+0x00000004 # header_block for type 0 (string) is located at offset 4
+0x00000000 # no bool type exists in overlay package -> no header_block
+0x00000009 # header_block for type 2 (integer) is located at offset 9
+0x00000003 # header_block for string; overlay IDs span 3 elements
+0x00000001 # the first string in target package is entry 1 == offset
+0x7f010000 # target 0x7f01001 -> overlay 0x7f010000
+0x00000000 # str2 not defined in overlay package
+0x7f010001 # target 0x7f010003 -> overlay 0x7f010001
+0x00000001 # header_block for integer; overlay IDs span 1 element
+0x00000000 # offset == 0
+0x7f020000 # target 0x7f030000 -> overlay 0x7f020000
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index 7fb7ae3e9fc8..57aaf24ec947 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -63,6 +63,10 @@ namespace android {
#endif
#endif
+#define IDMAP_MAGIC 0x706d6469
+// size measured in sizeof(uint32_t)
+#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t))
+
static void printToLogFunc(void* cookie, const char* txt)
{
LOGV("%s", txt);
@@ -214,6 +218,81 @@ static void deserializeInternal(const void* inData, Res_png_9patch* outData) {
outData->colors = (uint32_t*) data;
}
+static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes)
+{
+ if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+ LOGW("idmap assertion failed: size=%d bytes\n", sizeBytes);
+ return false;
+ }
+ if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess
+ LOGW("idmap assertion failed: invalid magic found (is 0x%08x, expected 0x%08x)\n",
+ *map, htodl(IDMAP_MAGIC));
+ return false;
+ }
+ return true;
+}
+
+static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, uint32_t* outValue)
+{
+ // see README for details on the format of map
+ if (!assertIdmapHeader(map, sizeBytes)) {
+ return UNKNOWN_ERROR;
+ }
+ map = map + IDMAP_HEADER_SIZE; // skip ahead to data segment
+ // size of data block, in uint32_t
+ const size_t size = (sizeBytes - ResTable::IDMAP_HEADER_SIZE_BYTES) / sizeof(uint32_t);
+ const uint32_t type = Res_GETTYPE(key) + 1; // add one, idmap stores "public" type id
+ const uint32_t entry = Res_GETENTRY(key);
+ const uint32_t typeCount = *map;
+
+ if (type > typeCount) {
+ LOGW("Resource ID map: type=%d exceeds number of types=%d\n", type, typeCount);
+ return UNKNOWN_ERROR;
+ }
+ if (typeCount > size) {
+ LOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, size);
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t typeOffset = map[type];
+ if (typeOffset == 0) {
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ if (typeOffset + 1 > size) {
+ LOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n",
+ typeOffset, size);
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t entryCount = map[typeOffset];
+ const uint32_t entryOffset = map[typeOffset + 1];
+ if (entryCount == 0 || entry < entryOffset || entry - entryOffset > entryCount - 1) {
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ const uint32_t index = typeOffset + 2 + entry - entryOffset;
+ if (index > size) {
+ LOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, size);
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ *outValue = map[index];
+
+ return NO_ERROR;
+}
+
+static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t *outId)
+{
+ if (!assertIdmapHeader(map, mapSize)) {
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t* p = map + IDMAP_HEADER_SIZE + 1;
+ while (*p == 0) {
+ ++p;
+ }
+ *outId = (map[*p + IDMAP_HEADER_SIZE + 2] >> 24) & 0x000000ff;
+ return NO_ERROR;
+}
+
Res_png_9patch* Res_png_9patch::deserialize(const void* inData)
{
if (sizeof(void*) != sizeof(int32_t)) {
@@ -1235,7 +1314,13 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const
struct ResTable::Header
{
- Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL) { }
+ Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
+ resourceIDMap(NULL), resourceIDMapSize(0) { }
+
+ ~Header()
+ {
+ free(resourceIDMap);
+ }
ResTable* const owner;
void* ownedData;
@@ -1246,6 +1331,8 @@ struct ResTable::Header
void* cookie;
ResStringPool values;
+ uint32_t* resourceIDMap;
+ size_t resourceIDMapSize;
};
struct ResTable::Type
@@ -1661,12 +1748,13 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const
return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1;
}
-status_t ResTable::add(const void* data, size_t size, void* cookie, bool copyData)
+status_t ResTable::add(const void* data, size_t size, void* cookie, bool copyData,
+ const void* idmap)
{
- return add(data, size, cookie, NULL, copyData);
+ return add(data, size, cookie, NULL, copyData, reinterpret_cast<const Asset*>(idmap));
}
-status_t ResTable::add(Asset* asset, void* cookie, bool copyData)
+status_t ResTable::add(Asset* asset, void* cookie, bool copyData, const void* idmap)
{
const void* data = asset->getBuffer(true);
if (data == NULL) {
@@ -1674,7 +1762,7 @@ status_t ResTable::add(Asset* asset, void* cookie, bool copyData)
return UNKNOWN_ERROR;
}
size_t size = (size_t)asset->getLength();
- return add(data, size, cookie, asset, copyData);
+ return add(data, size, cookie, asset, copyData, reinterpret_cast<const Asset*>(idmap));
}
status_t ResTable::add(ResTable* src)
@@ -1702,19 +1790,30 @@ status_t ResTable::add(ResTable* src)
}
status_t ResTable::add(const void* data, size_t size, void* cookie,
- Asset* asset, bool copyData)
+ Asset* asset, bool copyData, const Asset* idmap)
{
if (!data) return NO_ERROR;
Header* header = new Header(this);
header->index = mHeaders.size();
header->cookie = cookie;
+ if (idmap != NULL) {
+ const size_t idmap_size = idmap->getLength();
+ const void* idmap_data = const_cast<Asset*>(idmap)->getBuffer(true);
+ header->resourceIDMap = (uint32_t*)malloc(idmap_size);
+ if (header->resourceIDMap == NULL) {
+ delete header;
+ return (mError = NO_MEMORY);
+ }
+ memcpy((void*)header->resourceIDMap, idmap_data, idmap_size);
+ header->resourceIDMapSize = idmap_size;
+ }
mHeaders.add(header);
const bool notDeviceEndian = htods(0xf0) != 0xf0;
LOAD_TABLE_NOISY(
- LOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%p, asset=%p, copy=%d\n",
- data, size, cookie, asset, copyData));
+ LOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%p, asset=%p, copy=%d "
+ "idmap=%p\n", data, size, cookie, asset, copyData, idmap));
if (copyData || notDeviceEndian) {
header->ownedData = malloc(size);
@@ -1781,7 +1880,16 @@ status_t ResTable::add(const void* data, size_t size, void* cookie,
dtohl(header->header->packageCount));
return (mError=BAD_TYPE);
}
- if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
+ uint32_t idmap_id = 0;
+ if (idmap != NULL) {
+ uint32_t tmp;
+ if (getIdmapPackageId(header->resourceIDMap,
+ header->resourceIDMapSize,
+ &tmp) == NO_ERROR) {
+ idmap_id = tmp;
+ }
+ }
+ if (parsePackage((ResTable_package*)chunk, header, idmap_id) != NO_ERROR) {
return mError;
}
curPackage++;
@@ -1803,6 +1911,7 @@ status_t ResTable::add(const void* data, size_t size, void* cookie,
if (mError != NO_ERROR) {
LOGW("No string values found in resource table!");
}
+
TABLE_NOISY(LOGV("Returning from add with mError=%d\n", mError));
return mError;
}
@@ -1925,17 +2034,38 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag
size_t ip = grp->packages.size();
while (ip > 0) {
ip--;
+ int T = t;
+ int E = e;
const Package* const package = grp->packages[ip];
+ if (package->header->resourceIDMap) {
+ uint32_t overlayResID = 0x0;
+ status_t retval = idmapLookup(package->header->resourceIDMap,
+ package->header->resourceIDMapSize,
+ resID, &overlayResID);
+ if (retval == NO_ERROR && overlayResID != 0x0) {
+ // for this loop iteration, this is the type and entry we really want
+ LOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
+ T = Res_GETTYPE(overlayResID);
+ E = Res_GETENTRY(overlayResID);
+ } else {
+ // resource not present in overlay package, continue with the next package
+ continue;
+ }
+ }
const ResTable_type* type;
const ResTable_entry* entry;
const Type* typeClass;
- ssize_t offset = getEntry(package, t, e, &mParams, &type, &entry, &typeClass);
+ ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass);
if (offset <= 0) {
- if (offset < 0) {
+ // No {entry, appropriate config} pair found in package. If this
+ // package is an overlay package (ip != 0), this simply means the
+ // overlay package did not specify a default.
+ // Non-overlay packages are still required to provide a default.
+ if (offset < 0 && ip == 0) {
LOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
- resID, t, e, ip, (int)offset);
+ resID, T, E, ip, (int)offset);
return offset;
}
continue;
@@ -1965,13 +2095,16 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag
if (outSpecFlags != NULL) {
if (typeClass->typeSpecFlags != NULL) {
- *outSpecFlags |= dtohl(typeClass->typeSpecFlags[e]);
+ *outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
} else {
*outSpecFlags = -1;
}
}
-
- if (bestPackage != NULL && bestItem.isMoreSpecificThan(thisConfig)) {
+
+ if (bestPackage != NULL &&
+ (bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
+ // Discard thisConfig not only if bestItem is more specific, but also if the two configs
+ // are identical (diff == 0), or overlay packages will not take effect.
continue;
}
@@ -2165,21 +2298,45 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
TABLE_NOISY(LOGI("Building bag: %p\n", (void*)resID));
+ ResTable_config bestConfig;
+ memset(&bestConfig, 0, sizeof(bestConfig));
+
// Now collect all bag attributes from all packages.
size_t ip = grp->packages.size();
while (ip > 0) {
ip--;
+ int T = t;
+ int E = e;
const Package* const package = grp->packages[ip];
+ if (package->header->resourceIDMap) {
+ uint32_t overlayResID = 0x0;
+ status_t retval = idmapLookup(package->header->resourceIDMap,
+ package->header->resourceIDMapSize,
+ resID, &overlayResID);
+ if (retval == NO_ERROR && overlayResID != 0x0) {
+ // for this loop iteration, this is the type and entry we really want
+ LOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
+ T = Res_GETTYPE(overlayResID);
+ E = Res_GETENTRY(overlayResID);
+ } else {
+ // resource not present in overlay package, continue with the next package
+ continue;
+ }
+ }
const ResTable_type* type;
const ResTable_entry* entry;
const Type* typeClass;
- LOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, t, e);
- ssize_t offset = getEntry(package, t, e, &mParams, &type, &entry, &typeClass);
+ LOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E);
+ ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass);
LOGV("Resulting offset=%d\n", offset);
if (offset <= 0) {
- if (offset < 0) {
+ // No {entry, appropriate config} pair found in package. If this
+ // package is an overlay package (ip != 0), this simply means the
+ // overlay package did not specify a default.
+ // Non-overlay packages are still required to provide a default.
+ if (offset < 0 && ip == 0) {
if (set) free(set);
return offset;
}
@@ -2192,6 +2349,15 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
continue;
}
+ if (set != NULL && !type->config.isBetterThan(bestConfig, NULL)) {
+ continue;
+ }
+ bestConfig = type->config;
+ if (set) {
+ free(set);
+ set = NULL;
+ }
+
const uint16_t entrySize = dtohs(entry->size);
const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
? dtohl(((const ResTable_map_entry*)entry)->parent.ident) : 0;
@@ -2203,43 +2369,41 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
TABLE_NOISY(LOGI("Found map: size=%p parent=%p count=%d\n",
entrySize, parent, count));
- if (set == NULL) {
- // If this map inherits from another, we need to start
- // with its parent's values. Otherwise start out empty.
- TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
- entrySize, parent));
- if (parent) {
- const bag_entry* parentBag;
- uint32_t parentTypeSpecFlags = 0;
- const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
- const size_t NT = ((NP >= 0) ? NP : 0) + N;
- set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
- if (set == NULL) {
- return NO_MEMORY;
- }
- if (NP > 0) {
- memcpy(set+1, parentBag, NP*sizeof(bag_entry));
- set->numAttrs = NP;
- TABLE_NOISY(LOGI("Initialized new bag with %d inherited attributes.\n", NP));
- } else {
- TABLE_NOISY(LOGI("Initialized new bag with no inherited attributes.\n"));
- set->numAttrs = 0;
- }
- set->availAttrs = NT;
- set->typeSpecFlags = parentTypeSpecFlags;
+ // If this map inherits from another, we need to start
+ // with its parent's values. Otherwise start out empty.
+ TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
+ entrySize, parent));
+ if (parent) {
+ const bag_entry* parentBag;
+ uint32_t parentTypeSpecFlags = 0;
+ const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
+ const size_t NT = ((NP >= 0) ? NP : 0) + N;
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ if (NP > 0) {
+ memcpy(set+1, parentBag, NP*sizeof(bag_entry));
+ set->numAttrs = NP;
+ TABLE_NOISY(LOGI("Initialized new bag with %d inherited attributes.\n", NP));
} else {
- set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
- if (set == NULL) {
- return NO_MEMORY;
- }
+ TABLE_NOISY(LOGI("Initialized new bag with no inherited attributes.\n"));
set->numAttrs = 0;
- set->availAttrs = N;
- set->typeSpecFlags = 0;
}
+ set->availAttrs = NT;
+ set->typeSpecFlags = parentTypeSpecFlags;
+ } else {
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ set->numAttrs = 0;
+ set->availAttrs = N;
+ set->typeSpecFlags = 0;
}
if (typeClass->typeSpecFlags != NULL) {
- set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[e]);
+ set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
} else {
set->typeSpecFlags = -1;
}
@@ -3759,7 +3923,7 @@ ssize_t ResTable::getEntry(
}
status_t ResTable::parsePackage(const ResTable_package* const pkg,
- const Header* const header)
+ const Header* const header, uint32_t idmap_id)
{
const uint8_t* base = (const uint8_t*)pkg;
status_t err = validate_chunk(&pkg->header, sizeof(*pkg),
@@ -3793,8 +3957,12 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
Package* package = NULL;
PackageGroup* group = NULL;
- uint32_t id = dtohl(pkg->id);
- if (id != 0 && id < 256) {
+ uint32_t id = idmap_id != 0 ? idmap_id : dtohl(pkg->id);
+ // If at this point id == 0, pkg is an overlay package without a
+ // corresponding idmap. During regular usage, overlay packages are
+ // always loaded alongside their idmaps, but during idmap creation
+ // the package is temporarily loaded by itself.
+ if (id < 256) {
package = new Package(this, header, pkg);
if (package == NULL) {
@@ -3847,7 +4015,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
return (mError=err);
}
} else {
- LOG_ALWAYS_FATAL("Skins not supported!");
+ LOG_ALWAYS_FATAL("Package id out of range");
return NO_ERROR;
}
@@ -3998,6 +4166,136 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
return NO_ERROR;
}
+status_t ResTable::createIdmap(const ResTable& overlay, uint32_t originalCrc, uint32_t overlayCrc,
+ void** outData, size_t* outSize) const
+{
+ // see README for details on the format of map
+ if (mPackageGroups.size() == 0) {
+ return UNKNOWN_ERROR;
+ }
+ if (mPackageGroups[0]->packages.size() == 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ Vector<Vector<uint32_t> > map;
+ const PackageGroup* pg = mPackageGroups[0];
+ const Package* pkg = pg->packages[0];
+ size_t typeCount = pkg->types.size();
+ // starting size is header + first item (number of types in map)
+ *outSize = (IDMAP_HEADER_SIZE + 1) * sizeof(uint32_t);
+ const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name);
+ const uint32_t pkg_id = pkg->package->id << 24;
+
+ for (size_t typeIndex = 0; typeIndex < typeCount; ++typeIndex) {
+ ssize_t offset = -1;
+ const Type* typeConfigs = pkg->getType(typeIndex);
+ ssize_t mapIndex = map.add();
+ if (mapIndex < 0) {
+ return NO_MEMORY;
+ }
+ Vector<uint32_t>& vector = map.editItemAt(mapIndex);
+ for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
+ uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ resource_name resName;
+ if (!this->getResourceName(resID, &resName)) {
+ return UNKNOWN_ERROR;
+ }
+
+ const String16 overlayType(resName.type, resName.typeLen);
+ const String16 overlayName(resName.name, resName.nameLen);
+ uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
+ overlayName.size(),
+ overlayType.string(),
+ overlayType.size(),
+ overlayPackage.string(),
+ overlayPackage.size());
+ if (overlayResID != 0) {
+ // overlay package has package ID == 0, use original package's ID instead
+ overlayResID |= pkg_id;
+ }
+ vector.push(overlayResID);
+ if (overlayResID != 0 && offset == -1) {
+ offset = Res_GETENTRY(resID);
+ }
+#if 0
+ if (overlayResID != 0) {
+ LOGD("%s/%s 0x%08x -> 0x%08x\n",
+ String8(String16(resName.type)).string(),
+ String8(String16(resName.name)).string(),
+ resID, overlayResID);
+ }
+#endif
+ }
+
+ if (offset != -1) {
+ // shave off leading and trailing entries which lack overlay values
+ vector.removeItemsAt(0, offset);
+ vector.insertAt((uint32_t)offset, 0, 1);
+ while (vector.top() == 0) {
+ vector.pop();
+ }
+ // reserve space for number and offset of entries, and the actual entries
+ *outSize += (2 + vector.size()) * sizeof(uint32_t);
+ } else {
+ // no entries of current type defined in overlay package
+ vector.clear();
+ // reserve space for type offset
+ *outSize += 1 * sizeof(uint32_t);
+ }
+ }
+
+ if ((*outData = malloc(*outSize)) == NULL) {
+ return NO_MEMORY;
+ }
+ uint32_t* data = (uint32_t*)*outData;
+ *data++ = htodl(IDMAP_MAGIC);
+ *data++ = htodl(originalCrc);
+ *data++ = htodl(overlayCrc);
+ const size_t mapSize = map.size();
+ *data++ = htodl(mapSize);
+ size_t offset = mapSize;
+ for (size_t i = 0; i < mapSize; ++i) {
+ const Vector<uint32_t>& vector = map.itemAt(i);
+ const size_t N = vector.size();
+ if (N == 0) {
+ *data++ = htodl(0);
+ } else {
+ offset++;
+ *data++ = htodl(offset);
+ offset += N;
+ }
+ }
+ for (size_t i = 0; i < mapSize; ++i) {
+ const Vector<uint32_t>& vector = map.itemAt(i);
+ const size_t N = vector.size();
+ if (N == 0) {
+ continue;
+ }
+ *data++ = htodl(N - 1); // do not count the offset (which is vector's first element)
+ for (size_t j = 0; j < N; ++j) {
+ const uint32_t& overlayResID = vector.itemAt(j);
+ *data++ = htodl(overlayResID);
+ }
+ }
+
+ return NO_ERROR;
+}
+
+bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
+ uint32_t* pOriginalCrc, uint32_t* pOverlayCrc)
+{
+ const uint32_t* map = (const uint32_t*)idmap;
+ if (!assertIdmapHeader(map, sizeBytes)) {
+ return false;
+ }
+ *pOriginalCrc = map[1];
+ *pOverlayCrc = map[2];
+ return true;
+}
+
+
#ifndef HAVE_ANDROID_OS
#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string())
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 15570e47e6a1..fa84e938cab4 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -40,6 +40,7 @@ public:
mWantUTF16(false), mValues(false),
mCompressionMethod(0), mOutputAPKFile(NULL),
mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
+ mIsOverlayPackage(false),
mAutoAddOverlay(false), mAssetSourceDir(NULL), mProguardFile(NULL),
mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
@@ -92,6 +93,8 @@ public:
void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; }
const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; }
void setInstrumentationPackageNameOverride(const char * val) { mInstrumentationPackageNameOverride = val; }
+ bool getIsOverlayPackage() const { return mIsOverlayPackage; }
+ void setIsOverlayPackage(bool val) { mIsOverlayPackage = val; }
bool getAutoAddOverlay() { return mAutoAddOverlay; }
void setAutoAddOverlay(bool val) { mAutoAddOverlay = val; }
@@ -219,6 +222,7 @@ private:
const char* mOutputAPKFile;
const char* mManifestPackageNameOverride;
const char* mInstrumentationPackageNameOverride;
+ bool mIsOverlayPackage;
bool mAutoAddOverlay;
const char* mAssetSourceDir;
const char* mProguardFile;
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 266a02f25a17..1e63131eb5bf 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -68,6 +68,7 @@ void usage(void)
" [-S resource-sources [-S resource-sources ...]] "
" [-F apk-file] [-J R-file-dir] \\\n"
" [--product product1,product2,...] \\\n"
+ " [-o] \\\n"
" [raw-files-dir [raw-files-dir] ...]\n"
"\n"
" Package the android resources. It will read assets and resources that are\n"
@@ -105,6 +106,7 @@ void usage(void)
" -j specify a jar or zip file containing classes to include\n"
" -k junk path of file(s) added\n"
" -m make package directories under location specified by -J\n"
+ " -o create overlay package (ie only resources; expects <overlay-package> in manifest)\n"
#if 0
" -p pseudolocalize the default configuration\n"
#endif
@@ -275,6 +277,9 @@ int main(int argc, char* const argv[])
case 'm':
bundle.setMakePackageDirs(true);
break;
+ case 'o':
+ bundle.setIsOverlayPackage(true);
+ break;
#if 0
case 'p':
bundle.setPseudolocalize(true);
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 818c3c64cdb6..6459d0e8b663 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3674,7 +3674,9 @@ sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
{
sp<Package> p = mPackages.valueFor(package);
if (p == NULL) {
- if (mIsAppPackage) {
+ if (mBundle->getIsOverlayPackage()) {
+ p = new Package(package, 0x00);
+ } else if (mIsAppPackage) {
if (mHaveAppPackage) {
fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n"
"Use -x to create extended resources.\n");