diff options
Diffstat (limited to 'libs/utils/AssetManager.cpp')
-rw-r--r-- | libs/utils/AssetManager.cpp | 459 |
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; |