diff options
-rw-r--r-- | core/java/android/app/ActivityThread.java | 28 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 42 | ||||
-rw-r--r-- | core/java/android/content/pm/ActivityInfo.java | 63 | ||||
-rw-r--r-- | core/java/android/content/pm/ApplicationInfo.java | 2 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageParser.java | 4 | ||||
-rw-r--r-- | core/java/android/content/res/AssetManager.java | 9 | ||||
-rw-r--r-- | core/java/android/view/ContextThemeWrapper.java | 10 | ||||
-rw-r--r-- | core/jni/android_util_AssetManager.cpp | 85 | ||||
-rw-r--r-- | include/utils/AssetManager.h | 14 | ||||
-rw-r--r-- | include/utils/FileLock.h | 79 | ||||
-rw-r--r-- | include/utils/ResourceTypes.h | 45 | ||||
-rw-r--r-- | libs/utils/Android.mk | 1 | ||||
-rw-r--r-- | libs/utils/AssetManager.cpp | 459 | ||||
-rw-r--r-- | libs/utils/FileLock.cpp | 79 | ||||
-rw-r--r-- | libs/utils/ResourceTypes.cpp | 299 |
15 files changed, 1085 insertions, 134 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2bb412e..c30c442 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -226,6 +226,8 @@ public final class ActivityThread { if (assets.addAssetPath(resDir) == 0) { return null; } + + /* Attach theme information to the resulting AssetManager when appropriate. */ Configuration config = getConfiguration(); if (isThemable && config != null) { if (config.customTheme == null) { @@ -233,14 +235,8 @@ public final class ActivityThread { } if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) { - PackageInfo pi = getPackageInfo(config.customTheme.getThemePackageName(), 0); - if (pi != null) { - String themeResDir = pi.getResDir(); - if (assets.addAssetPath(themeResDir) != 0) { - assets.setThemePackageName(config.customTheme.getThemePackageName()); - } else { - Log.e(TAG, "Unable to add theme resdir=" + themeResDir); - } + if (!attachThemeAssets(assets, resDir, config.customTheme)) { + Log.e(TAG, "Failed to attach theme " + config.customTheme + " to resource '" + resDir + "'"); } } } @@ -253,7 +249,7 @@ public final class ActivityThread { + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); } - + synchronized (mPackages) { WeakReference<Resources> wr = mActiveResources.get(key); Resources existing = wr != null ? wr.get() : null; @@ -270,6 +266,20 @@ public final class ActivityThread { } } + private boolean attachThemeAssets(AssetManager assets, String resDir, CustomTheme theme) { + PackageInfo pi = getPackageInfo(theme.getThemePackageName(), 0); + if (pi != null) { + String themeResDir = pi.getResDir(); + if (assets.addAssetPath(themeResDir) != 0) { + assets.setThemePackageName(theme.getThemePackageName()); + return true; + } else { + Log.e(TAG, "Unable to add theme resdir=" + themeResDir); + } + } + return false; + } + /** * Creates the top level resources for the given package. * diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 1633ca0..5c4d803 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -291,53 +291,15 @@ class ContextImpl extends Context { mThemeResource = resid; } - private int determineDefaultThemeResource() { - if (getResources() != Resources.getSystem() && mPackageInfo.mApplicationInfo.isThemeable) { - try { - Configuration config = ActivityManagerNative.getDefault().getConfiguration(); - if (config.customTheme != null) { - int themeId = CustomTheme.getStyleId(this, - config.customTheme.getThemePackageName(), - config.customTheme.getThemeId()); - if (themeId == -1) { - CustomTheme defaultTheme = CustomTheme.getDefault(); - if (config.customTheme.equals(defaultTheme)) { - return com.android.internal.R.style.Theme; - } else { - themeId = CustomTheme.getStyleId(this, - defaultTheme.getThemePackageName(), - defaultTheme.getThemeId()); - if (themeId == -1) { - return com.android.internal.R.style.Theme; - } else { - return themeId; - } - } - } else { - return themeId; - } - } - } catch (RemoteException e) { - Log.e(TAG, "Unable to access configuration, reverting to original system default theme", e); - } - } - - /* Fallback... */ - return com.android.internal.R.style.Theme; - } - @Override public Resources.Theme getTheme() { if (mTheme == null) { - int themeId; if (mThemeResource == 0) { - themeId = determineDefaultThemeResource(); - } else { - themeId = mThemeResource; + mThemeResource = com.android.internal.R.style.Theme; } mTheme = mResources.newTheme(); - mTheme.applyStyle(themeId, true); + mTheme.applyStyle(mThemeResource, true); } return mTheme; } diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index ef62bc9..3257ebe 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -307,48 +307,6 @@ public class ActivityInfo extends ComponentInfo */ public int softInputMode; - /** - * isThemeable flag is not explicitly set - use isThemeable value from ApllicationInfo. - */ - private static final int ISTHEMEABLE_INHERITED = 0; - - /** - * isThemeable flag is explicitly set to false. - */ - private static final int ISTHEMEABLE_FALSE = 1; - - /** - * isThemeable flag is explicitly set to true. - */ - private static final int ISTHEMEABLE_TRUE = 2; - - /** - * Is given activity theme agnostic, i.e. behaves properly when default theme is changed. - * - * @deprecated Not fully supported; do not use. - * @hide - */ - private int isThemeable = ISTHEMEABLE_INHERITED; - - /** - * @deprecated Not fully supported; do not use. - * @hide - */ - public boolean getIsThemeable() { - if (isThemeable == ISTHEMEABLE_INHERITED) { - return applicationInfo != null && applicationInfo.isThemeable; - } - return isThemeable != ISTHEMEABLE_FALSE; - } - - /** - * @deprecated Not fully supported; do not use. - * @hide - */ - public void setIsThemeable(boolean value) { - isThemeable = value? ISTHEMEABLE_TRUE : ISTHEMEABLE_FALSE; - } - public ActivityInfo() { } @@ -363,7 +321,6 @@ public class ActivityInfo extends ComponentInfo screenOrientation = orig.screenOrientation; configChanges = orig.configChanges; softInputMode = orig.softInputMode; - isThemeable = orig.isThemeable; } /** @@ -377,23 +334,6 @@ public class ActivityInfo extends ComponentInfo return theme != 0 ? theme : applicationInfo.theme; } - /** - * @deprecated Not fully supported; do not use. - * @hide - */ - public final boolean isThemeable() { - switch (isThemeable) { - case ISTHEMEABLE_TRUE: - return true; - - case ISTHEMEABLE_INHERITED: - return applicationInfo.isThemeable; - - default: - return false; - } - } - public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); if (permission != null) { @@ -412,7 +352,6 @@ public class ActivityInfo extends ComponentInfo + " configChanges=0x" + Integer.toHexString(configChanges) + " softInputMode=0x" + Integer.toHexString(softInputMode)); } - pw.println(prefix + "isThemeable=" + isThemeable); super.dumpBack(pw, prefix); } @@ -437,7 +376,6 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(screenOrientation); dest.writeInt(configChanges); dest.writeInt(softInputMode); - dest.writeInt(isThemeable); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -461,6 +399,5 @@ public class ActivityInfo extends ComponentInfo screenOrientation = source.readInt(); configChanges = source.readInt(); softInputMode = source.readInt(); - isThemeable = source.readInt(); } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 978ff73..a76c523 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -353,7 +353,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Is given application theme agnostic, i.e. behaves properly when default theme is changed. * {@hide} */ - public boolean isThemeable = false; + public boolean isThemeable = true; private static final String PLUTO_SCHEMA = "http://www.w3.org/2001/pluto.html"; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 6006ac0..b77ea32 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1472,9 +1472,7 @@ public class PackageParser { continue; } String attrName = attrs.getAttributeName(i); - if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME)) { - ai.setIsThemeable(attrs.getAttributeBooleanValue(i, false)); - } else if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME)) { + if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME)) { ai.configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE; } } diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 4b5d769..56b5cde 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -82,7 +82,6 @@ public final class AssetManager { private String mAppName; private boolean mThemeSupport; - private String mThemePackageName; /** * Create a new AssetManager containing only the basic system assets. @@ -695,17 +694,13 @@ public final class AssetManager { * Get package name of current theme (may return null). * {@hide} */ - public final String getThemePackageName() { - return mThemePackageName; - } + public native final String getThemePackageName(); /** * Sets package name for current theme (null is allowed). * {@hide} */ - public final void setThemePackageName(String packageName) { - mThemePackageName = packageName; - } + public native final void setThemePackageName(String packageName); /** * Determine whether the state in this asset manager is up-to-date with diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index e82af80..3c05d05 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -124,11 +124,11 @@ public class ContextThemeWrapper extends ContextWrapper { } if (mThemeResource == 0) { - return mBase.getTheme(); - } else { - initializeTheme(); - return mTheme; + mThemeResource = com.android.internal.R.style.Theme; } + initializeTheme(); + + return mTheme; } @Override @@ -158,7 +158,7 @@ public class ContextThemeWrapper extends ContextWrapper { } private void initializeTheme() { - final boolean first = (mTheme == null); + final boolean first = mTheme == null; if (first) { mTheme = getResources().newTheme(); Resources.Theme theme = mBase.getTheme(); diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index c8f4599..24ff670 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -37,6 +37,8 @@ #include <stdio.h> +#define REDIRECT_NOISY(x) //x + namespace android { // ---------------------------------------------------------------------------- @@ -718,17 +720,23 @@ static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject } const ResTable& res(am->getResources()); + uint32_t ref = res.lookupRedirectionMap(ident); + if (ref == 0) { + ref = ident; + } else { + REDIRECT_NOISY(LOGW("PERFORMED REDIRECT OF ident=0x%08x FOR ref=0x%08x\n", ident, ref)); + } + Res_value value; ResTable_config config; uint32_t typeSpecFlags; - ssize_t block = res.getResource(ident, &value, false, &typeSpecFlags, &config); + ssize_t block = res.getResource(ref, &value, false, &typeSpecFlags, &config); #if THROW_ON_BAD_ID if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } #endif - uint32_t ref = ident; if (resolve) { block = res.resolveReference(&value, block, &ref); #if THROW_ON_BAD_ID @@ -993,6 +1001,20 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla // Now lock down the resource object and start pulling stuff from it. res.lock(); + // Apply theme redirections to the referenced styles. + if (defStyleRes != 0) { + uint32_t ref = res.lookupRedirectionMap(defStyleRes); + if (ref != 0) { + defStyleRes = ref; + } + } + if (style != 0) { + uint32_t ref = res.lookupRedirectionMap(style); + if (ref != 0) { + style = ref; + } + } + // Retrieve the default style bag, if requested. const ResTable::bag_entry* defStyleEnt = NULL; uint32_t defStyleTypeSetFlags = 0; @@ -1116,6 +1138,17 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla value.dataType = Res_value::TYPE_NULL; } + // One final test for a resource redirection from the applied theme. + if (resid != 0) { + uint32_t redirect = res.lookupRedirectionMap(resid); + if (redirect != 0) { + REDIRECT_NOISY(LOGW("deep REDIRECT FROM resid=0x%08x TO redirect=0x%08x\n", resid, redirect)); + block = res.getResource(redirect, &value, false, &typeSetFlags, &config); + block = res.resolveReference(&value, block, &redirect); + resid = redirect; + } + } + DEBUG_STYLES(LOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data)); @@ -1367,6 +1400,17 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz value.dataType = Res_value::TYPE_NULL; } + // One final test for a resource redirection from the applied theme. + if (resid != 0) { + uint32_t redirect = res.lookupRedirectionMap(resid); + if (redirect != 0) { + REDIRECT_NOISY(LOGW("array REDIRECT FROM resid=0x%08x TO redirect=0x%08x\n", resid, redirect)); + block = res.getResource(redirect, &value, false, &typeSetFlags, &config); + block = res.resolveReference(&value, block, &redirect); + resid = redirect; + } + } + //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); // Write the final value back to Java. @@ -1723,6 +1767,38 @@ static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, return AssetManager::getGlobalCount(); } +static void android_content_AssetManager_setThemePackageName(JNIEnv* env, jobject clazz, + jstring packageName) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return; + } + + if (packageName != NULL) { + const char* packageName8 = env->GetStringUTFChars(packageName, NULL); + am->setThemePackageName(packageName8); + env->ReleaseStringUTFChars(packageName, packageName8); + } else { + am->setThemePackageName(NULL); + } +} + +static jstring android_content_AssetManager_getThemePackageName(JNIEnv* env, jobject clazz) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return NULL; + } + + const char* packageName = am->getThemePackageName(); + if (packageName == NULL) { + return NULL; + } + + return env->NewStringUTF(packageName); +} + static jboolean android_content_AssetManager_removeAssetPath(JNIEnv* env, jobject clazz, jstring packageName, jstring path) { @@ -1891,6 +1967,11 @@ static JNINativeMethod gAssetManagerMethods[] = { { "splitThemePackage","(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)I", (void*) android_content_AssetManager_splitThemePackage }, + // Dynamic theme package support. + { "setThemePackageName", "(Ljava/lang/String;)V", + (void*) android_content_AssetManager_setThemePackageName }, + { "getThemePackageName", "()Ljava/lang/String;", + (void*) android_content_AssetManager_getThemePackageName }, { "removeAssetPath", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*) android_content_AssetManager_removeAssetPath }, { "updateResourcesWithAssetPath", "(Ljava/lang/String;)I", diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h index e1a11e1..57d6ef2 100644 --- a/include/utils/AssetManager.h +++ b/include/utils/AssetManager.h @@ -198,6 +198,9 @@ public: */ void getLocales(Vector<String8>* locales) const; + void setThemePackageName(const char* packageName); + const char* getThemePackageName(); + /* * Remove existing source for assets. It can be either a directory (for * deleting assets as raw files on the disk) or a ZIP file. @@ -216,7 +219,12 @@ private: FileType type; }; - void updateResTableFromAssetPath(ResTable *rt, const asset_path& ap, void *cookie) const; + SharedBuffer* generateRedirections(SharedBuffer* entriesByTypeBuf, ResTable* rt, + const char* themePackageName, const char16_t* resPackageName); + bool generateAndWriteRedirections(ResTable* rt, const char* themePackageName, + const char16_t* resPackageName, const char* redirPath, bool isFramework) const; + void loadRedirectionMappings(ResTable* rt) const; + void updateResTableFromAssetPath(ResTable* rt, const asset_path& ap, void* cookie) const; Asset* openInPathLocked(const char* fileName, AccessMode mode, const asset_path& path); Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode, @@ -333,6 +341,10 @@ private: char* mLocale; char* mVendor; + // If non-null, represents the theme package from which to construct the + // resource redirection map used by ResTable. + char* mThemePackageName; + mutable ResTable* mResources; ResTable_config* mConfig; diff --git a/include/utils/FileLock.h b/include/utils/FileLock.h new file mode 100644 index 0000000..fde5938 --- /dev/null +++ b/include/utils/FileLock.h @@ -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. + */ + +#ifndef __LIBS_FILE_LOCK_H +#define __LIBS_FILE_LOCK_H + +#include <fcntl.h> + +namespace android { + +/* + * Object oriented interface for flock. Implements reference counting so that + * multiple levels of locks on the same object instance is possible. + */ +class FileLock { +public: + FileLock(const char* fileName); + + /* + * Lock the file. A balanced call to unlock is required even if the lock + * fails. + */ + bool lock(int openFlags=O_RDONLY, mode_t fileCreateMode=0755) { + mRefCount++; + if (mFd == -1) { + return doLock(openFlags, fileCreateMode); + } else { + return true; + } + } + + /* + * Call this when mapping is no longer needed. + */ + void unlock(void) { + if (--mRefCount <= 0) { + delete this; + } + } + + /* + * Return the name of the file this map came from, if known. + */ + const char* getFileName(void) const { return mFileName; } + + /* + * Return the open file descriptor, if locked; -1 otherwise. + */ + int getFileDescriptor(void) const { return mFd; } + +protected: + // don't delete objects; call unlock() + ~FileLock(void); + + bool doLock(int openFlags, mode_t fileCreateMode); + +private: + + int mRefCount; // reference count + int mFd; // file descriptor, if locked + char* mFileName; // original file name, if known +}; + +}; // namespace android + +#endif // __LIBS_FILE_LOCK_H diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index 0d47cfd..b2ad4e1 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -1711,6 +1711,9 @@ public: bool copyData=false); status_t add(ResTable* src); + status_t addRedirections(int package, const char* dataPath); + void clearRedirections(); + status_t getError() const; void uninit(); @@ -1756,6 +1759,8 @@ public: uint32_t* inoutTypeSpecFlags = NULL, ResTable_config* outConfig = NULL) const; + uint32_t lookupRedirectionMap(uint32_t resID) const; + enum { TMP_BUFFER_SIZE = 16 }; @@ -1979,7 +1984,7 @@ private: const ResTable_package* const pkg, const Header* const header); void print_value(const Package* pkg, const Res_value& value) const; - + mutable Mutex mLock; status_t mError; @@ -1995,6 +2000,44 @@ private: // Mapping from resource package IDs to indices into the internal // package array. uint8_t mPackageMap[256]; + + // Represents the resource lookup table for a specific package. + class PackageResMap { + public: + ~PackageResMap(); + + int package; + + // Do the heavy lifting to read from a specific package resource map + // file. + static PackageResMap* createFromCache(int package, const char* cachePath); + + // Returns 0 if the lookup finds no mapping. + uint32_t lookup(int type, int entry); + + // Inserts a SharedBuffer array into the res map structure. + void insert(int type, const uint32_t* entries); + + private: + PackageResMap(); + + bool parseMap(const unsigned char* basePtr, + const unsigned char* endPtr); + + uint32_t* parseMapType(const unsigned char* basePtr, + const unsigned char* endPtr); + + // Sparse array representing all entries, organized into two layers: + // first by type, then by entry id. The result of each lookup will be + // a qualified resource ID in the theme package scope. Underneath is a + // SharedBuffer on both layers which indicates the size. + uint32_t** mEntriesByType; + }; + + // Resource redirection mapping provided by the applied theme (if there is + // any). Resources requested which are found in this map will be + // automatically redirected to the appropriate themed value. + Vector<PackageResMap*> mRedirectionMap; }; } // namespace android 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 |