summaryrefslogtreecommitdiffstats
path: root/libs/utils/AssetManager.cpp
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/utils/AssetManager.cpp
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/utils/AssetManager.cpp')
-rw-r--r--libs/utils/AssetManager.cpp459
1 files changed, 458 insertions, 1 deletions
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;