summaryrefslogtreecommitdiffstats
path: root/libs
diff options
context:
space:
mode:
authorJosh Guilfoyle <jasta00@gmail.com>2010-12-06 16:29:38 -0800
committerJosh Guilfoyle <Josh.Guilfoyle@T-Mobile.com>2010-12-22 22:45:21 -0800
commitfce264d5ecb0ed901d874fe9e8215c4d0b7f507f (patch)
treeb569cab745b53de975b31877041018628650bf75 /libs
parent238a60da5785b86bb290b4889e76ead329e6fba2 (diff)
downloadframeworks_base-fce264d5ecb0ed901d874fe9e8215c4d0b7f507f.zip
frameworks_base-fce264d5ecb0ed901d874fe9e8215c4d0b7f507f.tar.gz
frameworks_base-fce264d5ecb0ed901d874fe9e8215c4d0b7f507f.tar.bz2
Introduced an asset redirection system to the theme engine.
Previously, the theme engine required careful redirection of only the highest-level "theme" style element, relying then on the theme attribute lookups to satisfy the demands of all themeable apps. This approach created significant challenges which required, in some cases, heavy modification for existing applications to support the engine. Custom widgets and themed components required special theme support in order to be reachable by the style engine alone. Similarly, a number of clumsy hacks were required around the framework to support certain components such as menus and tab widgets. The asset redirection system allows the theme to specify a redirection table which is consulted for nearly all final resource resolutions. This essentially means that once the theme is applied, any original resource being redirected would no longer be accessible by the AssetManager, instead being redirected to the matching resource in the theme itself. In order for this to function efficiently it is necessary to create cache files mapping resource integers from one package to another, even if those integers will change across releases. For this, one-time name to integer conversions occur, with the results written in a data format suitable for efficient reads for each AssetManager created. Currently this format must be parsed and cannot be directly mmaped though this will be addressed in future commits. Change-Id: Ie36e74314c600a72e03e568675a38801b7acd1ee
Diffstat (limited to 'libs')
-rw-r--r--libs/utils/Android.mk1
-rw-r--r--libs/utils/AssetManager.cpp459
-rw-r--r--libs/utils/FileLock.cpp79
-rw-r--r--libs/utils/ResourceTypes.cpp299
4 files changed, 836 insertions, 2 deletions
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index ae9b06a..24cb9a2 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -25,6 +25,7 @@ commonSources:= \
CallStack.cpp \
Debug.cpp \
FileMap.cpp \
+ FileLock.cpp \
Flattenable.cpp \
RefBase.cpp \
ResourceTypes.cpp \
diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp
index 4591f3e..eeebe05 100644
--- a/libs/utils/AssetManager.cpp
+++ b/libs/utils/AssetManager.cpp
@@ -33,11 +33,16 @@
#include <utils/Log.h>
#include <utils/Timers.h>
#include <utils/threads.h>
+#include <utils/FileLock.h>
#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <errno.h>
#include <assert.h>
+#define REDIRECT_NOISY(x) //x
+
using namespace android;
/*
@@ -52,6 +57,8 @@ static const char* kSystemAssets = "framework/framework-res.apk";
static const char* kExcludeExtension = ".EXCLUDE";
+static const char* kThemeResCacheDir = "res-cache/";
+
static Asset* const kExcludedAsset = (Asset*) 0xd000000d;
static volatile int32_t gCount = 0;
@@ -70,6 +77,7 @@ int32_t AssetManager::getGlobalCount()
AssetManager::AssetManager(CacheMode cacheMode)
: mLocale(NULL), mVendor(NULL),
+ mThemePackageName(NULL),
mResources(NULL), mConfig(new ResTable_config),
mCacheMode(cacheMode), mCacheValid(false)
{
@@ -89,6 +97,10 @@ AssetManager::~AssetManager(void)
// don't have a String class yet, so make sure we clean up
delete[] mLocale;
delete[] mVendor;
+
+ if (mThemePackageName != NULL) {
+ delete[] mThemePackageName;
+ }
}
bool AssetManager::addAssetPath(const String8& path, void** cookie)
@@ -371,6 +383,429 @@ FileType AssetManager::getFileType(const char* fileName)
return kFileTypeRegular;
}
+static void createDirIfNecessary(const char* path, mode_t mode, struct stat *statbuf)
+{
+ if (lstat(path, statbuf) != 0) {
+ if (mkdir(path, mode) != 0) {
+ LOGE("mkdir(%s,%04o) failed: %s\n", path, (int)mode, strerror(errno));
+ }
+ }
+}
+
+static SharedBuffer* addToEntriesByTypeBuffer(SharedBuffer* buf, uint32_t from, uint32_t to)
+{
+ size_t currentSize = (buf != NULL) ? buf->size() : 0;
+
+ int type = Res_GETTYPE(from)+1;
+ int entry = Res_GETENTRY(from);
+
+ size_t typeSize = (type+1) * sizeof(uint32_t*);
+ unsigned int requestSize = roundUpPower2(typeSize);
+ if (typeSize > currentSize) {
+ unsigned int requestSize = roundUpPower2(typeSize);
+ if (buf == NULL) {
+ buf = SharedBuffer::alloc(requestSize);
+ } else {
+ buf = buf->editResize(requestSize);
+ }
+ memset((unsigned char*)buf->data()+currentSize, 0, requestSize-currentSize);
+ }
+
+ uint32_t** entriesByType = (uint32_t**)buf->data();
+ uint32_t* entries = entriesByType[type];
+ SharedBuffer* entriesBuf = (entries != NULL) ? SharedBuffer::bufferFromData(entries) : NULL;
+ currentSize = (entriesBuf != NULL) ? entriesBuf->size() : 0;
+ size_t entrySize = (entry+1) * sizeof(uint32_t);
+ if (entrySize > currentSize) {
+ unsigned int requestSize = roundUpPower2(entrySize);
+ if (entriesBuf == NULL) {
+ entriesBuf = SharedBuffer::alloc(requestSize);
+ } else {
+ entriesBuf = entriesBuf->editResize(requestSize);
+ }
+ memset((unsigned char*)entriesBuf->data()+currentSize, 0, requestSize-currentSize);
+ entriesByType[type] = (uint32_t*)entriesBuf->data();
+ }
+ entries = (uint32_t*)entriesBuf->data();
+ entries[entry] = to;
+
+ return buf;
+}
+
+// TODO: Terrible, terrible I/O error handling here!
+static bool writeRedirections(const char* redirPath, SharedBuffer* entriesByTypeBuf)
+{
+ REDIRECT_NOISY(LOGW("writing %s\n", redirPath));
+
+ FILE* fp = fopen(redirPath, "w");
+ if (!fp) {
+ LOGE("fopen(%s,r) failed: %s\n", redirPath, strerror(errno));
+ return false;
+ }
+
+ uint16_t version = 1;
+ fwrite(&version, sizeof(version), 1, fp);
+
+ uint16_t totalTypes = 0;
+ size_t typeSize = (entriesByTypeBuf != NULL) ? entriesByTypeBuf->size() / sizeof(uint32_t*) : 0;
+ uint32_t** entriesByType = (uint32_t**)entriesByTypeBuf->data();
+ for (size_t i=0; i<typeSize; i++) {
+ uint32_t* entries = entriesByType[i];
+ if (entries != NULL) {
+ totalTypes++;
+ }
+ }
+
+ REDIRECT_NOISY(LOGW("writing %d total types\n", (int)totalTypes));
+ fwrite(&totalTypes, sizeof(totalTypes), 1, fp);
+
+ // Start offset for the first type table.
+ uint32_t typeSectionOffset = 4 + (9 * totalTypes);
+
+ for (size_t i=0; i<typeSize; i++) {
+ uint32_t* entries = entriesByType[i];
+ if (entries != NULL) {
+ uint8_t type = i;
+ fwrite(&type, sizeof(type), 1, fp);
+ size_t entrySize = SharedBuffer::bufferFromData(entries)->size() / sizeof(uint32_t);
+ size_t numEntries = 0;
+ for (size_t j=0; j<entrySize; j++) {
+ if (entries[j] != 0) {
+ numEntries++;
+ }
+ }
+ REDIRECT_NOISY(LOGW("%d entries for type %d\n", (int)numEntries, (int)type));
+ fwrite(&typeSectionOffset, sizeof(typeSectionOffset), 1, fp);
+ uint32_t typeSectionLength = numEntries * 6;
+ fwrite(&typeSectionLength, sizeof(typeSectionLength), 1, fp);
+ typeSectionOffset += typeSectionLength;
+ }
+ }
+
+ for (size_t i=0; i<typeSize; i++) {
+ uint32_t* entries = entriesByType[i];
+ if (entries != NULL) {
+ REDIRECT_NOISY(LOGW("writing for type %d...\n", i));
+ size_t entrySize = SharedBuffer::bufferFromData(entries)->size() / sizeof(uint32_t);
+ for (size_t j=0; j<entrySize; j++) {
+ uint32_t resID = entries[j];
+ if (resID != 0) {
+ uint16_t entryIndex = j;
+ REDIRECT_NOISY(LOGW("writing 0x%04x => 0x%08x\n", entryIndex, resID));
+ fwrite(&entryIndex, sizeof(entryIndex), 1, fp);
+ fwrite(&resID, sizeof(resID), 1, fp);
+ }
+ }
+ SharedBuffer::bufferFromData(entries)->release();
+ }
+ }
+
+ if (entriesByTypeBuf != NULL) {
+ entriesByTypeBuf->release();
+ }
+
+ fclose(fp);
+
+ REDIRECT_NOISY(LOGW("written...\n"));
+
+ return true;
+}
+
+// Crude, lame brute force way to generate the initial framework redirections
+// for testing. This code should be generalized and follow a much better OO
+// structure.
+static SharedBuffer* generateFrameworkRedirections(SharedBuffer* entriesByTypeBuf, ResTable* rt,
+ const char* themePackageName, const char* redirPath)
+{
+ REDIRECT_NOISY(LOGW("generateFrameworkRedirections: themePackageName=%s\n", themePackageName));
+
+ // HACK HACK HACK
+ if (strcmp(themePackageName, "com.tmobile.theme.Androidian") != 0) {
+ LOGW("EEP, UNEXPECTED PACKAGE!");
+ return entriesByTypeBuf;
+ }
+
+ rt->lock();
+
+ // Load up a bag for the user-supplied theme.
+ String16 type("style");
+ String16 name("Androidian");
+ String16 package(themePackageName);
+ uint32_t ident = rt->identifierForName(name.string(), name.size(), type.string(), type.size(),
+ package.string(), package.size());
+ if (ident == 0) {
+ LOGW("unable to locate theme identifier %s:%s/%s\n", String8(package).string(),
+ String8(type).string(), String8(name).string());
+ rt->unlock();
+ return entriesByTypeBuf;
+ }
+
+ const ResTable::bag_entry* themeEnt = NULL;
+ ssize_t N = rt->getBagLocked(ident, &themeEnt);
+ const ResTable::bag_entry* endThemeEnt = themeEnt + N;
+
+ // ...and a bag for the framework default.
+ const ResTable::bag_entry* frameworkEnt = NULL;
+ N = rt->getBagLocked(0x01030005, &frameworkEnt);
+ const ResTable::bag_entry* endFrameworkEnt = frameworkEnt + N;
+
+ // The first entry should be for the theme itself.
+ entriesByTypeBuf = addToEntriesByTypeBuffer(entriesByTypeBuf, 0x01030005, ident);
+
+ // Now compare them and infer resource redirections for attributes that
+ // remap to different styles. This works by essentially lining up all the
+ // sorted attributes from each theme and detected TYPE_REFERENCE entries
+ // that point to different resources. When we find such a mismatch, we'll
+ // create a resource redirection from the original framework resource ID to
+ // the one in the theme. This lets us do things like automatically find
+ // redirections for @android:style/Widget.Button by looking at how the
+ // theme overrides the android:attr/buttonStyle attribute.
+ REDIRECT_NOISY(LOGW("delta between 0x01030005 and 0x%08x:\n", ident));
+ for (; frameworkEnt < endFrameworkEnt; frameworkEnt++) {
+ if (frameworkEnt->map.value.dataType != Res_value::TYPE_REFERENCE) {
+ continue;
+ }
+
+ uint32_t curIdent = frameworkEnt->map.name.ident;
+
+ // Walk along the theme entry looking for a match.
+ while (themeEnt < endThemeEnt && curIdent > themeEnt->map.name.ident) {
+ themeEnt++;
+ }
+ // Match found, compare the references.
+ if (themeEnt < endThemeEnt && curIdent == themeEnt->map.name.ident) {
+ if (themeEnt->map.value.data != frameworkEnt->map.value.data) {
+ entriesByTypeBuf = addToEntriesByTypeBuffer(entriesByTypeBuf,
+ frameworkEnt->map.value.data, themeEnt->map.value.data);
+ REDIRECT_NOISY(LOGW(" generated mapping from 0x%08x => 0x%08x (by attr 0x%08x)\n", frameworkEnt->map.value.data,
+ themeEnt->map.value.data, curIdent));
+ }
+ themeEnt++;
+ }
+
+ // Exhausted the theme, bail early.
+ if (themeEnt >= endThemeEnt) {
+ break;
+ }
+ }
+
+ rt->unlock();
+
+ return entriesByTypeBuf;
+}
+
+static SharedBuffer* parseRedirections(SharedBuffer* buf, ResTable* rt,
+ ResXMLTree& xml, String16& themePackage, String16& resPackage)
+{
+ ResXMLTree::event_code_t eventType = xml.getEventType();
+ REDIRECT_NOISY(LOGW("initial eventType=%d\n", (int)eventType));
+ size_t len;
+ while (eventType != ResXMLTree::END_DOCUMENT) {
+ if (eventType == ResXMLTree::START_TAG) {
+ String8 outerTag(xml.getElementName(&len));
+ if (outerTag == "resource-redirections") {
+ REDIRECT_NOISY(LOGW("got resource-redirections tag\n"));
+ xml.next();
+ while ((eventType = xml.getEventType()) != ResXMLTree::END_TAG) {
+ if (eventType == ResXMLTree::START_TAG) {
+ String8 itemTag(xml.getElementName(&len));
+ if (itemTag == "item") {
+ REDIRECT_NOISY(LOGW("got item tag\n"));
+ ssize_t nameIdx = xml.indexOfAttribute(NULL, "name");
+ size_t fromLen;
+ const char16_t* fromName = NULL;
+ size_t toLen;
+ const char16_t* toName = NULL;
+ if (nameIdx >= 0) {
+ fromName = xml.getAttributeStringValue(nameIdx, &fromLen);
+ REDIRECT_NOISY(LOGW(" got from %s\n", String8(fromName).string()));
+ }
+ if (xml.next() == ResXMLTree::TEXT) {
+ toName = xml.getText(&toLen);
+ REDIRECT_NOISY(LOGW(" got to %s\n", String8(toName).string()));
+ xml.next();
+ }
+ if (toName != NULL && fromName != NULL) {
+ // fromName should look "drawable/foo", so we'll
+ // let identifierForName parse that part of it, but
+ // make sure to provide the background ourselves.
+ // TODO: we should check that the package isn't
+ // already in the string and error out if it is...
+ uint32_t fromIdent = rt->identifierForName(fromName, fromLen, NULL, 0,
+ resPackage.string(), resPackage.size());
+ if (fromIdent == 0) {
+ LOGW("Failed to locate identifier for resource %s:%s\n",
+ String8(fromName, fromLen).string(), String8(resPackage).string());
+ } else {
+ uint32_t toIdent = rt->identifierForName(toName, toLen, NULL, 0,
+ themePackage.string(), themePackage.size());
+ if (toIdent == 0) {
+ LOGW("Failed to locate identifier for resource %s:%s\n",
+ String8(toName, toLen).string(), String8(themePackage).string());
+ } else {
+ REDIRECT_NOISY(LOGW("adding fromIdent=0x%08x to toIdent=0x%08x\n", fromIdent, toIdent));
+ buf = addToEntriesByTypeBuffer(buf, fromIdent, toIdent);
+ }
+ }
+ }
+ } else {
+ REDIRECT_NOISY(LOGW("unexpected tag %s\n", itemTag.string()));
+ }
+ } else if (eventType == ResXMLTree::END_DOCUMENT) {
+ return buf;
+ }
+ xml.next();
+ }
+ }
+ }
+
+ eventType = xml.next();
+ }
+
+ return buf;
+}
+
+// Generate redirections from XML meta data. This code should be moved into
+// the Java space at some point, generated by a special service.
+SharedBuffer* AssetManager::generateRedirections(SharedBuffer* entriesByTypeBuf,
+ ResTable* rt, const char* themePackageName,
+ const char16_t* resPackageName)
+{
+ REDIRECT_NOISY(LOGW("generateRedirections: themePackageName=%s; resPackageName=%s\n",
+ themePackageName, String8(resPackageName).string()));
+
+ String16 type("xml");
+ String16 name(resPackageName);
+ name.replaceAll('.', '_');
+ String16 package(themePackageName);
+ uint32_t xmlIdent = rt->identifierForName(name.string(), name.size(), type.string(), type.size(),
+ package.string(), package.size());
+ REDIRECT_NOISY(LOGW("xmlIdent=0x%08x from %s:%s/%s\n", xmlIdent,
+ String8(package).string(), String8(type).string(), String8(name).string()));
+ if (xmlIdent != 0) {
+ // All this junk is being simulated from the Java side implementation.
+ // This is very clumsy and poorly thought through/tested. This code
+ // will eventually be merged into the Java layer.
+ Res_value value;
+ ssize_t block = rt->getResource(xmlIdent, &value);
+ block = rt->resolveReference(&value, block, &xmlIdent);
+ if (block < 0 || value.dataType != Res_value::TYPE_STRING) {
+ LOGE("Bad redirection XML resource #0x%08x\n", xmlIdent);
+ } else {
+ size_t len;
+ const char16_t* str = rt->valueToString(&value, block, NULL, &len);
+ void* cookie = rt->getTableCookie(block);
+ const size_t whichAssetPath = ((size_t)cookie)-1;
+ Asset* asset = openNonAssetInPathLocked(
+ String8(str).string(), Asset::ACCESS_BUFFER,
+ mAssetPaths.itemAt(whichAssetPath));
+ if (asset == NULL || asset == kExcludedAsset) {
+ LOGE("XML resource %s not found in package\n", String8(str).string());
+ } else {
+ ResXMLTree xml;
+ status_t err = xml.setTo(asset->getBuffer(true), asset->getLength());
+
+ xml.restart();
+
+ String16 resPackage(resPackageName);
+ entriesByTypeBuf = parseRedirections(entriesByTypeBuf, rt, xml,
+ package, resPackage);
+
+ xml.uninit();
+
+ asset->close();
+ delete asset;
+ }
+ }
+ }
+
+ return entriesByTypeBuf;
+}
+
+bool AssetManager::generateAndWriteRedirections(ResTable* rt,
+ const char* themePackageName, const char16_t* resPackageName,
+ const char* redirPath, bool isFramework) const
+{
+ // FIXME: the const is a lie!!!
+ AssetManager* am = (AssetManager*)this;
+
+ SharedBuffer* buf = NULL;
+ if (isFramework) {
+ // Special framework theme heuristic...
+ buf = generateFrameworkRedirections(buf, rt, themePackageName, redirPath);
+ }
+ // Generate redirections from the package XML.
+ buf = am->generateRedirections(buf, rt, themePackageName, resPackageName);
+
+ return writeRedirections(redirPath, buf);
+}
+
+void AssetManager::loadRedirectionMappings(ResTable* rt) const
+{
+ rt->clearRedirections();
+
+ if (mThemePackageName != NULL) {
+ const char* data = getenv("ANDROID_DATA");
+ LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
+
+ // Create the basic directory structure on demand.
+ struct stat statbuf;
+ String8 basePath(data);
+ basePath.appendPath(kThemeResCacheDir);
+ createDirIfNecessary(basePath.string(), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, &statbuf);
+ basePath.appendPath(mThemePackageName);
+ createDirIfNecessary(basePath.string(), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, &statbuf);
+
+ String8 themeDirLockPath(basePath);
+ themeDirLockPath.append(".lck");
+
+ FileLock* themeDirLock = new FileLock(themeDirLockPath.string());
+ themeDirLock->lock();
+
+ // Load (generating if necessary) the cache files for each installed
+ // package in this ResTable, excluding the framework's "android"
+ // package.
+ bool hasFramework = false;
+ const size_t N = rt->getBasePackageCount();
+ for (size_t i=0; i<N; i++) {
+ uint32_t packageId = rt->getBasePackageId(i);
+
+ // No need to regenerate the 0x01 framework resources.
+ if (packageId == 0x7f) {
+ String8 redirPath(basePath);
+ const char16_t* resPackageName = rt->getBasePackageName(i);
+ redirPath.appendPath(String8(resPackageName));
+
+ if (lstat(redirPath.string(), &statbuf) != 0) {
+ generateAndWriteRedirections(rt, mThemePackageName,
+ resPackageName, redirPath.string(), false);
+ }
+
+ rt->addRedirections(packageId, redirPath.string());
+ } else if (packageId == 0x01) {
+ hasFramework = true;
+ }
+ }
+
+ // Handle the "android" package space as a special case using some
+ // fancy heuristics.
+ if (hasFramework) {
+ String8 frameworkRedirPath(basePath);
+ frameworkRedirPath.appendPath("android");
+
+ if (lstat(frameworkRedirPath.string(), &statbuf) != 0) {
+ generateAndWriteRedirections(rt, mThemePackageName,
+ String16("android").string(),
+ frameworkRedirPath.string(), true);
+ }
+
+ rt->addRedirections(0x01, frameworkRedirPath.string());
+ }
+
+ themeDirLock->unlock();
+ }
+}
+
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
@@ -401,6 +836,7 @@ const ResTable* AssetManager::getResTable(bool required) const
const asset_path& ap = mAssetPaths.itemAt(i);
updateResTableFromAssetPath(rt, ap, (void*)(i+1));
}
+ loadRedirectionMappings(rt);
}
if (required && !rt) LOGW("Unable to find resources file resources.arsc");
@@ -1768,14 +2204,34 @@ int AssetManager::ZipSet::getIndex(const String8& zip) const
return mZipPath.size()-1;
}
+/*
+ * Set the currently applied theme package name.
+ *
+ * This information is used when constructing the ResTable's resource
+ * redirection map.
+ */
+void AssetManager::setThemePackageName(const char* packageName)
+{
+ if (mThemePackageName != NULL) {
+ delete[] mThemePackageName;
+ }
+ mThemePackageName = strdupNew(packageName);
+}
+
+const char* AssetManager::getThemePackageName()
+{
+ return mThemePackageName;
+}
+
bool AssetManager::updateWithAssetPath(const String8& path, void** cookie)
{
bool res = addAssetPath(path, cookie);
ResTable* rt = mResources;
- if (res && rt != NULL && ((size_t)*cookie == mAssetPaths.size())) {
+ if (res && rt != NULL && ((size_t)*cookie == mAssetPaths.size())) {
AutoMutex _l(mLock);
const asset_path& ap = mAssetPaths.itemAt((size_t)*cookie - 1);
updateResTableFromAssetPath(rt, ap, *cookie);
+ loadRedirectionMappings(rt);
}
return res;
}
@@ -1809,6 +2265,7 @@ bool AssetManager::removeAssetPath(const String8 &packageName, const String8 &as
return false;
}
+ rt->clearRedirections();
rt->removeAssetsByCookie(packageName, (void *)cookie);
return true;
diff --git a/libs/utils/FileLock.cpp b/libs/utils/FileLock.cpp
new file mode 100644
index 0000000..c279b86
--- /dev/null
+++ b/libs/utils/FileLock.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// File locking utility
+//
+
+#define LOG_TAG "filelock"
+
+#include <utils/FileLock.h>
+#include <utils/Log.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <errno.h>
+#include <assert.h>
+
+using namespace android;
+
+/*
+ * Constructor. Create an unlocked object.
+ */
+FileLock::FileLock(const char* fileName)
+ : mRefCount(0), mFd(-1), mFileName(strdup(fileName))
+{
+ assert(mFileName != NULL);
+}
+
+/*
+ * Destructor.
+ */
+FileLock::~FileLock(void)
+{
+ assert(mRefCount == 0);
+
+ if (mFileName != NULL) {
+ free(mFileName);
+ }
+ if (mFd >= 0) {
+ if (flock(mFd, LOCK_UN) != 0) {
+ LOGE("flock(%s,LOCK_UN) failed: %s\n", mFileName, strerror(errno));
+ }
+ if (close(mFd) != 0) {
+ LOGE("close(%s) failed: %s\n", mFileName, strerror(errno));
+ }
+ }
+}
+
+bool FileLock::doLock(int openFlags, mode_t fileCreateMode)
+{
+ int fd = open(mFileName, openFlags | O_CREAT, fileCreateMode);
+ if (fd == -1) {
+ return false;
+ }
+
+ if (flock(fd, LOCK_EX) != 0) {
+ LOGE("flock(%s,LOCK_EX) failed: %s\n", mFileName, strerror(errno));
+ close(fd);
+ return false;
+ }
+
+ mFd = fd;
+ return true;
+}
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index e1ce1c2..319b8d9 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -26,6 +26,7 @@
#include <utils/String8.h>
#include <utils/TextOutput.h>
#include <utils/Log.h>
+#include <utils/misc.h>
#include <stdlib.h>
#include <string.h>
@@ -44,6 +45,7 @@
#define TABLE_SUPER_NOISY(x) //x
#define LOAD_TABLE_NOISY(x) //x
#define TABLE_THEME(x) //x
+#define REDIRECT_NOISY(x) //x
namespace android {
@@ -1411,6 +1413,13 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
const bag_entry* bag;
uint32_t bagTypeSpecFlags = 0;
mTable.lock();
+ uint32_t redirect = mTable.lookupRedirectionMap(resID);
+ if (redirect != 0 || resID == 0x01030005) {
+ REDIRECT_NOISY(LOGW("applyStyle: PERFORMED REDIRECT OF ident=0x%08x FOR redirect=0x%08x\n", resID, redirect));
+ }
+ if (redirect != 0) {
+ resID = redirect;
+ }
const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags);
TABLE_NOISY(LOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N));
if (N < 0) {
@@ -1836,6 +1845,8 @@ void ResTable::uninit()
mPackageGroups.clear();
mHeaders.clear();
+
+ clearRedirections();
}
bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const
@@ -2039,6 +2050,26 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
return blockIndex;
}
+uint32_t ResTable::lookupRedirectionMap(uint32_t resID) const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+
+ const int p = Res_GETPACKAGE(resID)+1;
+ const int t = Res_GETTYPE(resID)+1;
+ const int e = Res_GETENTRY(resID);
+
+ const size_t N = mRedirectionMap.size();
+ for (size_t i=0; i<N; i++) {
+ PackageResMap* resMap = mRedirectionMap[i];
+ if (resMap->package == p) {
+ return resMap->lookup(t, e);
+ }
+ }
+ return 0;
+}
+
const char16_t* ResTable::valueToString(
const Res_value* value, size_t stringBlock,
char16_t tmpBuffer[TMP_BUFFER_SIZE], size_t* outLen)
@@ -2214,7 +2245,19 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
if (parent) {
const bag_entry* parentBag;
uint32_t parentTypeSpecFlags = 0;
- const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
+ uint32_t parentRedirect = lookupRedirectionMap(parent);
+ uint32_t parentActual = parent;
+ if (parentRedirect != 0 || parent == 0x01030005) {
+ if (parentRedirect == resID) {
+ REDIRECT_NOISY(LOGW("applyStyle(parent): ignoring circular redirect from parent=0x%08x to parentRedirect=0x%08x\n", parent, parentRedirect));
+ } else {
+ REDIRECT_NOISY(LOGW("applyStyle(parent): PERFORMED REDIRECT OF parent=0x%08x FOR parentRedirect=0x%08x\n", parent, parentRedirect));
+ if (parentRedirect != 0) {
+ parentActual = parentRedirect;
+ }
+ }
+ }
+ const ssize_t NP = getBagLocked(parentActual, &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) {
@@ -4053,6 +4096,260 @@ void ResTable::removeAssetsByCookie(const String8 &packageName, void* cookie)
}
}
+ResTable::PackageResMap::PackageResMap()
+ : mEntriesByType(NULL)
+{
+}
+
+ResTable::PackageResMap::~PackageResMap()
+{
+ if (mEntriesByType != NULL) {
+ SharedBuffer* buf = SharedBuffer::bufferFromData(mEntriesByType);
+ const size_t N = buf->size() / sizeof(mEntriesByType[0]);
+ for (size_t i = 0; i < N; i++) {
+ uint32_t* entries = mEntriesByType[i];
+ if (entries != NULL) {
+ SharedBuffer::bufferFromData(entries)->release();
+ }
+ }
+ buf->release();
+ }
+}
+
+uint32_t* ResTable::PackageResMap::parseMapType(const unsigned char* ptr, const unsigned char* end)
+{
+ SharedBuffer* buf = NULL;
+ for (; ptr + 6 <= end; ptr += 6) {
+ uint16_t entry = *(uint16_t*)ptr;
+ uint32_t resID = *(uint32_t*)(ptr + 2);
+ REDIRECT_NOISY(LOGW("map type got entry=0x%04x, resID=0x%08x\n", entry, resID));
+ size_t currentSize = (buf != NULL) ? buf->size() : 0;
+ size_t entrySize = (entry+1) * sizeof(resID);
+ if (entrySize > currentSize) {
+ unsigned int requestSize = roundUpPower2(entrySize);
+ REDIRECT_NOISY(LOGW("allocating buffer (%p) at requestSize=%d\n", buf, (int)requestSize));
+ if (buf == NULL) {
+ buf = SharedBuffer::alloc(requestSize);
+ } else {
+ buf = buf->editResize(requestSize);
+ }
+ memset((unsigned char*)buf->data()+currentSize, 0, requestSize - currentSize);
+ REDIRECT_NOISY(LOGW("allocated buf=%p\n", buf));
+ }
+ uint32_t* entries = (uint32_t*)buf->data();
+ entries[entry] = resID;
+ }
+ return (buf != NULL) ? (uint32_t*)buf->data() : NULL;
+}
+
+/*
+ * Parse the resource mappings file.
+ *
+ * The format of this file begins with a simple header identifying the number
+ * of types inside, followed by a table mapping each type to an offset further
+ * in the file. At each offset mentioned is a set of resource ID mappings to
+ * be parsed out and applied to a sparse array that is ultimately used to
+ * lookup resource redirections (the indices are entries in the associated
+ * package space and the values are the replacement resID's from the theme
+ * itself)
+ *
+ * Detailed information of each section:
+ *
+ * | bytes | description
+ * |-------+-----------------------
+ * | 2 | file format version (first version is 1)
+ * | 2 | number of resource types (think Res_GETTYPE) in the mapping
+ *
+ * For each resource type:
+ *
+ * +-------+-----------------------
+ * | 1 | type identifier
+ * | 4 | file offset containing the entry mapping
+ * | 4 | length of the entry mapping section for this type
+ * ...
+ *
+ * At each file offset mentioned in the type header:
+ *
+ * +-------+-----------------------
+ * | 2 | entry id to be replaced (combined with the type and package this
+ * | | forms a resID)
+ * | 4 | resID in the theme space which is to replace the previous entry
+ * ...
+ */
+bool ResTable::PackageResMap::parseMap(const unsigned char* basePtr,
+ const unsigned char* endPtr)
+{
+ LOG_FATAL_IF(mEntriesByType != NULL, "parseMap must only be called once");
+
+ if (basePtr + 4 > endPtr) {
+ return false;
+ }
+ uint16_t version = *(uint16_t*)basePtr;
+ uint16_t numTypes = *(uint16_t*)(basePtr + 2);
+ const unsigned char* headerPtr = basePtr + 4;
+
+ REDIRECT_NOISY(LOGW("file version=%d\n", version));
+ REDIRECT_NOISY(LOGW("read %d numTypes\n", numTypes));
+
+ while (numTypes-- > 0) {
+ uint8_t type;
+ uint32_t* entries = NULL;
+ if (headerPtr + 9 < endPtr) {
+ type = *(uint8_t*)headerPtr;
+ uint32_t offset = *(uint32_t*)(headerPtr + 1);
+ uint32_t length = *(uint32_t*)(headerPtr + 5);
+ headerPtr += 9;
+ REDIRECT_NOISY(LOGW("got type=0x%02x\n", type));
+ if (basePtr + offset + length <= endPtr) {
+ REDIRECT_NOISY(LOGW("parsing type...\n"));
+ const unsigned char* entryStartPtr = basePtr + offset;
+ const unsigned char* entryEndPtr = entryStartPtr + length;
+ entries = parseMapType(entryStartPtr, entryEndPtr);
+ }
+ }
+ if (entries == NULL) {
+ return false;
+ }
+ REDIRECT_NOISY(LOGW("inserting type 0x%02x with %p\n", type, entries));
+ insert(type, entries);
+ }
+
+ return true;
+}
+
+/*
+ * Load the redirection map from the supplied map path.
+ *
+ * The path is expected to be a directory containing individual map cache files
+ * for each package that is to have resources redirected. Only those packages
+ * that are included in this ResTable will be loaded into the redirection map.
+ * For this reason, this method should be called only after all resource
+ * bundles have been added to the table.
+ */
+status_t ResTable::addRedirections(int package, const char* cachePath)
+{
+ LOGV("Adding redirections for package 0x%02x at %s\n", package, cachePath);
+
+ if (package != 0x01 && package != 0x7f) {
+ REDIRECT_NOISY(LOGW("invalid package 0x%02x: should be either 0x01 (android) or 0x7f (application)\n", package));
+ return BAD_TYPE;
+ }
+
+ ResTable::PackageResMap* resMap = ResTable::PackageResMap::createFromCache(package, cachePath);
+ if (resMap != NULL) {
+ REDIRECT_NOISY(LOGW("loaded cache stuff for cachePath=%s (package=%d)\n", cachePath, package));
+ mRedirectionMap.add(resMap);
+ return NO_ERROR;
+ } else {
+ REDIRECT_NOISY(LOGW("failed to parse redirection path at %s\n", cachePath));
+ return BAD_TYPE;
+ }
+}
+
+void ResTable::clearRedirections()
+{
+ const size_t N = mRedirectionMap.size();
+ for (size_t i=0; i<N; i++) {
+ PackageResMap* resMap = mRedirectionMap[i];
+ delete resMap;
+ }
+ mRedirectionMap.clear();
+}
+
+ResTable::PackageResMap* ResTable::PackageResMap::createFromCache(int package, const char* cachePath)
+{
+ FILE* cacheFile = fopen(cachePath, "r");
+ if (cacheFile == NULL) {
+ REDIRECT_NOISY(LOGW("unable to open resource mapping path %s: %s\n", cachePath, strerror(errno)));
+ return NULL;
+ }
+
+ if (fseek(cacheFile, 0, SEEK_END) != 0) {
+ fclose(cacheFile);
+ return NULL;
+ }
+
+ long length = ftell(cacheFile);
+ REDIRECT_NOISY(LOGW("file length is %d\n", (int)length));
+ if (length < 4) {
+ fclose(cacheFile);
+ return NULL;
+ }
+
+ ResTable::PackageResMap* resMap = NULL;
+ FileMap* fileMap = new FileMap;
+ if (fileMap != NULL) {
+ if (fileMap->create(cachePath, fileno(cacheFile), 0, length, true)) {
+ REDIRECT_NOISY(LOGW("successfully mapped %s\n", cachePath));
+ resMap = new ResTable::PackageResMap();
+ if (resMap != NULL) {
+ resMap->package = package;
+ const unsigned char* ptr = (const unsigned char*)fileMap->getDataPtr();
+ if (!resMap->parseMap(ptr, ptr + length)) {
+ REDIRECT_NOISY(LOGW("failed to parse map!\n"));
+ delete resMap;
+ resMap = NULL;
+ }
+ }
+ } else {
+ REDIRECT_NOISY(LOGW("unable to map '%s': %s\n", cachePath, strerror(errno)));
+ }
+
+ fileMap->release();
+ }
+
+ fclose(cacheFile);
+
+ return resMap;
+}
+
+uint32_t ResTable::PackageResMap::lookup(int type, int entry)
+{
+ if (mEntriesByType == NULL) {
+ return 0;
+ }
+ size_t maxTypes = SharedBuffer::bufferFromData(mEntriesByType)->size() /
+ sizeof(mEntriesByType[0]);
+ if (type < 0 || type >= maxTypes) {
+ return 0;
+ }
+ uint32_t* entries = mEntriesByType[type];
+ if (entries == NULL) {
+ return 0;
+ }
+ size_t maxEntries = SharedBuffer::bufferFromData(entries)->size() /
+ sizeof(entries[0]);
+ if (entry < 0 || entry >= maxEntries) {
+ return 0;
+ }
+ return entries[entry];
+}
+
+void ResTable::PackageResMap::insert(int type, const uint32_t* entries)
+{
+ SharedBuffer* buf = NULL;
+ size_t currentSize = 0;
+ if (mEntriesByType != NULL) {
+ buf = SharedBuffer::bufferFromData(mEntriesByType);
+ currentSize = buf->size();
+ }
+ size_t typeSize = (type+1) * sizeof(uint32_t*);
+ if (typeSize > currentSize) {
+ unsigned int requestSize = roundUpPower2(typeSize);
+ REDIRECT_NOISY(LOGW("allocating new type buffer (%p) at size requestSize=%d\n", buf, requestSize));
+ if (buf == NULL) {
+ buf = SharedBuffer::alloc(requestSize);
+ } else {
+ buf = buf->editResize(requestSize);
+ }
+ memset((unsigned char*)buf->data()+currentSize, 0, requestSize - currentSize);
+ REDIRECT_NOISY(LOGW("allocated new type buffer %p\n", buf));
+ }
+ uint32_t** entriesByType = (uint32_t**)buf->data();
+ entriesByType[type] = (uint32_t*)entries;
+ mEntriesByType = entriesByType;
+}
+
#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string())
#ifndef HAVE_ANDROID_OS