resolved conflicts for merge of 87b3c0dc
to honeycomb-plus-aosp
Change-Id: Ia1a0024aabf531438203eb9fea3a10dd15eabe53
This commit is contained in:
4
core/tests/overlaytests/Android.mk
Normal file
4
core/tests/overlaytests/Android.mk
Normal file
@ -0,0 +1,4 @@
|
||||
# Dummy makefile to halt recursive directory traversal.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
10
core/tests/overlaytests/OverlayTest/Android.mk
Normal file
10
core/tests/overlaytests/OverlayTest/Android.mk
Normal file
@ -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)
|
10
core/tests/overlaytests/OverlayTest/AndroidManifest.xml
Normal file
10
core/tests/overlaytests/OverlayTest/AndroidManifest.xml
Normal file
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.android.overlaytest;
|
||||
|
||||
public class WithOverlayTest extends OverlayBaseTest {
|
||||
public WithOverlayTest() {
|
||||
mWithOverlay = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.android.overlaytest;
|
||||
|
||||
public class WithoutOverlayTest extends OverlayBaseTest {
|
||||
public WithoutOverlayTest() {
|
||||
mWithOverlay = false;
|
||||
}
|
||||
}
|
14
core/tests/overlaytests/OverlayTestOverlay/Android.mk
Normal file
14
core/tests/overlaytests/OverlayTestOverlay/Android.mk
Normal file
@ -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)
|
@ -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>
|
Binary file not shown.
After Width: | Height: | Size: 399 B |
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="phoneTypeWork">Jobb</string>
|
||||
</resources>
|
@ -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>
|
15
core/tests/overlaytests/README
Normal file
15
core/tests/overlaytests/README
Normal file
@ -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.
|
89
core/tests/overlaytests/runtests.sh
Executable file
89
core/tests/overlaytests/runtests.sh
Executable file
@ -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)
|
@ -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);
|
||||
|
@ -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;
|
||||
@ -1983,6 +1983,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);
|
||||
@ -1996,7 +2014,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(
|
||||
@ -2005,7 +2023,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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
@ -1290,7 +1369,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;
|
||||
@ -1301,6 +1386,8 @@ struct ResTable::Header
|
||||
void* cookie;
|
||||
|
||||
ResStringPool values;
|
||||
uint32_t* resourceIDMap;
|
||||
size_t resourceIDMapSize;
|
||||
};
|
||||
|
||||
struct ResTable::Type
|
||||
@ -1716,12 +1803,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) {
|
||||
@ -1729,7 +1817,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)
|
||||
@ -1757,19 +1845,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);
|
||||
@ -1836,7 +1935,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++;
|
||||
@ -1858,6 +1966,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;
|
||||
}
|
||||
@ -2002,17 +2111,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, desiredConfig, &type, &entry, &typeClass);
|
||||
ssize_t offset = getEntry(package, T, E, desiredConfig, &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);
|
||||
rc = offset;
|
||||
goto out;
|
||||
}
|
||||
@ -2044,13 +2174,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;
|
||||
}
|
||||
|
||||
@ -2250,21 +2383,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;
|
||||
}
|
||||
@ -2277,6 +2434,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;
|
||||
@ -2288,43 +2454,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;
|
||||
} 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 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;
|
||||
} 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;
|
||||
}
|
||||
@ -3862,7 +4026,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),
|
||||
@ -3896,8 +4060,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) {
|
||||
@ -3950,7 +4118,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;
|
||||
}
|
||||
|
||||
@ -4101,6 +4269,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())
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -3681,7 +3681,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");
|
||||
|
Reference in New Issue
Block a user