diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-01-29 18:20:45 -0800 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2014-03-25 12:09:56 -0700 |
commit | de898ff42912bd7ca1bfb099cd439562496765a4 (patch) | |
tree | 849b591a99a7e6a8fd790aedca3afff6f6b6eade | |
parent | 05f79758cd2688f89444a38baba326a0a1c1a438 (diff) | |
download | frameworks_base-de898ff42912bd7ca1bfb099cd439562496765a4.zip frameworks_base-de898ff42912bd7ca1bfb099cd439562496765a4.tar.gz frameworks_base-de898ff42912bd7ca1bfb099cd439562496765a4.tar.bz2 |
Shared library resource support
Shared libraries can now export resources for applications
to use.
Exporting resources works the same way the framework exports
resources, by defining the public symbols in res/values/public.xml.
Building a shared library requires aapt to be invoked with the
--shared-lib option. Shared libraries will be assigned a package
ID of 0x00 at build-time. At runtime, all loaded shared libraries
will be assigned a new package ID.
Currently, shared libraries should not import other shared libraries,
as those dependencies will not be loaded at runtime.
At runtime, reflection is used to update the package ID of resource
symbols in the shared library's R class file. The package name of
the R class file is assumed to be the same as the shared library's
package name declared in its manifest. This will be customizable in
a future commit.
See /tests/SharedLibrary/ for examples of a shared library and its
client.
Bug:12724178
Change-Id: I60c0cb8ab87849f8f8a1a13431562fe8603020a7
38 files changed, 889 insertions, 121 deletions
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index c5fc941..1153f38 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -119,7 +119,8 @@ namespace { int parse_manifest(const void *data, size_t size, const char *target_package_name) { - ResXMLTree parser(data, size); + ResXMLTree parser; + parser.setTo(data, size); if (parser.getError() != NO_ERROR) { ALOGD("%s failed to init xml parser, error=0x%08x\n", __FUNCTION__, parser.getError()); return -1; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 69ada6a..965f815 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1592,10 +1592,10 @@ public final class ActivityThread { /** * Creates the top level resources for the given package. */ - Resources getTopLevelResources(String resDir, String[] overlayDirs, + Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, LoadedApk pkgInfo) { - return mResourcesManager.getTopLevelResources(resDir, overlayDirs, displayId, + return mResourcesManager.getTopLevelResources(resDir, overlayDirs, libDirs, displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 8165fa1..0615bd9 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -805,7 +805,7 @@ final class ApplicationPackageManager extends PackageManager { } Resources r = mContext.mMainThread.getTopLevelResources( app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir, - app.resourceDirs, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo); + app.resourceDirs, null, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo); if (r != null) { return r; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 87f47a1..589c82f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2044,8 +2044,9 @@ class ContextImpl extends Context { || (compatInfo != null && compatInfo.applicationScale != resources.getCompatibilityInfo().applicationScale)) { resources = mResourcesManager.getTopLevelResources( - packageInfo.getResDir(), packageInfo.getOverlayDirs(), displayId, - overrideConfiguration, compatInfo, activityToken); + packageInfo.getResDir(), packageInfo.getOverlayDirs(), + packageInfo.getApplicationInfo().sharedLibraryFiles, + displayId, overrideConfiguration, compatInfo, activityToken); } } mResources = resources; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index d409352..3ae8bfc 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -41,6 +41,7 @@ import android.os.Trace; import android.os.UserHandle; import android.util.AndroidRuntimeException; import android.util.Slog; +import android.util.SparseArray; import android.view.DisplayAdjustments; import android.view.Display; @@ -48,6 +49,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.net.URL; import java.util.Enumeration; @@ -485,7 +488,7 @@ public final class LoadedApk { public Resources getResources(ActivityThread mainThread) { if (mResources == null) { mResources = mainThread.getTopLevelResources(mResDir, mOverlayDirs, - Display.DEFAULT_DISPLAY, null, this); + mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this); } return mResources; } @@ -530,10 +533,101 @@ public final class LoadedApk { } } } - + + // Rewrite the R 'constants' for all library apks. + SparseArray<String> packageIdentifiers = getAssets(mActivityThread) + .getAssignedPackageIdentifiers(); + final int N = packageIdentifiers.size(); + for (int i = 0; i < N; i++) { + final int id = packageIdentifiers.keyAt(i); + if (id == 0x01 || id == 0x7f) { + continue; + } + + rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id); + } + return app; } + private void rewriteIntField(Field field, int packageId) throws IllegalAccessException { + int requiredModifiers = Modifier.STATIC | Modifier.PUBLIC; + int bannedModifiers = Modifier.FINAL; + + int mod = field.getModifiers(); + if ((mod & requiredModifiers) != requiredModifiers || + (mod & bannedModifiers) != 0) { + throw new IllegalArgumentException("Field " + field.getName() + + " is not rewritable"); + } + + if (field.getType() != int.class && field.getType() != Integer.class) { + throw new IllegalArgumentException("Field " + field.getName() + + " is not an integer"); + } + + try { + int resId = field.getInt(null); + field.setInt(null, (resId & 0x00ffffff) | (packageId << 24)); + } catch (IllegalAccessException e) { + // This should not occur (we check above if we can write to it) + throw new IllegalArgumentException(e); + } + } + + private void rewriteIntArrayField(Field field, int packageId) { + int requiredModifiers = Modifier.STATIC | Modifier.PUBLIC; + + if ((field.getModifiers() & requiredModifiers) != requiredModifiers) { + throw new IllegalArgumentException("Field " + field.getName() + + " is not rewritable"); + } + + if (field.getType() != int[].class) { + throw new IllegalArgumentException("Field " + field.getName() + + " is not an integer array"); + } + + try { + int[] array = (int[]) field.get(null); + for (int i = 0; i < array.length; i++) { + array[i] = (array[i] & 0x00ffffff) | (packageId << 24); + } + } catch (IllegalAccessException e) { + // This should not occur (we check above if we can write to it) + throw new IllegalArgumentException(e); + } + } + + private void rewriteRValues(ClassLoader cl, String packageName, int id) { + try { + final Class<?> rClazz = cl.loadClass(packageName + ".R"); + Class<?>[] declaredClasses = rClazz.getDeclaredClasses(); + for (Class<?> clazz : declaredClasses) { + try { + if (clazz.getSimpleName().equals("styleable")) { + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == int[].class) { + rewriteIntArrayField(field, id); + } + } + + } else { + for (Field field : clazz.getDeclaredFields()) { + rewriteIntField(field, id); + } + } + } catch (Exception e) { + throw new IllegalArgumentException("Failed to rewrite R values for " + + clazz.getName(), e); + } + } + + } catch (Exception e) { + throw new IllegalArgumentException("Failed to rewrite R values", e); + } + } + public void removeContextRegistrations(Context context, String who, String what) { final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled(); diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 728f372..a67faa0 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -144,14 +144,16 @@ public class ResourcesManager { * Creates the top level Resources for applications with the given compatibility info. * * @param resDir the resource directory. + * @param overlayDirs the resource overlay directories. + * @param libDirs the shared library resource dirs this app references. * @param compatInfo the compability info. Must not be null. * @param token the application token for determining stack bounds. */ - public Resources getTopLevelResources(String resDir, String[] overlayDirs, int displayId, - Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { + public Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs, + int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, + IBinder token) { final float scale = compatInfo.applicationScale; - ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, - token); + ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token); Resources r; synchronized (this) { // Resources is app scale dependent. @@ -186,6 +188,15 @@ public class ResourcesManager { } } + if (libDirs != null) { + for (String libDir : libDirs) { + if (assets.addAssetPath(libDir) == 0) { + Slog.w(TAG, "Asset path '" + libDir + + "' does not exist or contains no resources."); + } + } + } + //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics dm = getDisplayMetricsLocked(displayId); Configuration config; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 2f8dd53..0c04401 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -18,6 +18,7 @@ package android.content.res; import android.os.ParcelFileDescriptor; import android.util.Log; +import android.util.SparseArray; import android.util.TypedValue; import java.io.FileNotFoundException; @@ -266,11 +267,9 @@ public final class AssetManager { } } - /*package*/ final CharSequence getPooledString(int block, int id) { - //System.out.println("Get pooled: block=" + block - // + ", id=#" + Integer.toHexString(id) - // + ", blocks=" + mStringBlocks); - return mStringBlocks[block-1].get(id); + /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) { + // Cookies map to string blocks starting at 1. + return mStringBlocks[cookie - 1].get(id); } /** @@ -740,6 +739,11 @@ public final class AssetManager { /** * {@hide} */ + public native final SparseArray<String> getAssignedPackageIdentifiers(); + + /** + * {@hide} + */ public native static final int getGlobalAssetCount(); /** diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index baf887e..d7199ff 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -834,7 +834,7 @@ public class TypedArray { } return null; } - return mAssets.getPooledString(cookie, data[index+AssetManager.STYLE_DATA]); + return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]); } /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) { diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index 3ad357f..2f4d69b 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -375,7 +375,7 @@ final class XmlBlock { boolean defaultValue) { int t = nativeGetAttributeDataType(mParseState, idx); // Note: don't attempt to convert any other types, because - // we want to count on appt doing the conversion for us. + // we want to count on aapt doing the conversion for us. if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { return nativeGetAttributeData(mParseState, idx) != 0; @@ -385,7 +385,7 @@ final class XmlBlock { public int getAttributeResourceValue(int idx, int defaultValue) { int t = nativeGetAttributeDataType(mParseState, idx); // Note: don't attempt to convert any other types, because - // we want to count on appt doing the conversion for us. + // we want to count on aapt doing the conversion for us. if (t == TypedValue.TYPE_REFERENCE) { return nativeGetAttributeData(mParseState, idx); } @@ -394,7 +394,7 @@ final class XmlBlock { public int getAttributeIntValue(int idx, int defaultValue) { int t = nativeGetAttributeDataType(mParseState, idx); // Note: don't attempt to convert any other types, because - // we want to count on appt doing the conversion for us. + // we want to count on aapt doing the conversion for us. if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { return nativeGetAttributeData(mParseState, idx); @@ -404,7 +404,7 @@ final class XmlBlock { public int getAttributeUnsignedIntValue(int idx, int defaultValue) { int t = nativeGetAttributeDataType(mParseState, idx); // Note: don't attempt to convert any other types, because - // we want to count on appt doing the conversion for us. + // we want to count on aapt doing the conversion for us. if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { return nativeGetAttributeData(mParseState, idx); @@ -414,7 +414,7 @@ final class XmlBlock { public float getAttributeFloatValue(int idx, float defaultValue) { int t = nativeGetAttributeDataType(mParseState, idx); // Note: don't attempt to convert any other types, because - // we want to count on appt doing the conversion for us. + // we want to count on aapt doing the conversion for us. if (t == TypedValue.TYPE_FLOAT) { return Float.intBitsToFloat( nativeGetAttributeData(mParseState, idx)); diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 9dde701..4147608 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -73,6 +73,13 @@ static struct assetmanager_offsets_t jfieldID mObject; } gAssetManagerOffsets; +static struct sparsearray_offsets_t +{ + jclass classObject; + jmethodID constructor; + jmethodID put; +} gSparseArrayOffsets; + jclass g_stringClass = NULL; // ---------------------------------------------------------------------------- @@ -905,6 +912,26 @@ static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject c return str; } +static jobject android_content_AssetManager_getAssignedPackageIdentifiers(JNIEnv* env, jobject clazz) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return 0; + } + + const ResTable& res = am->getResources(); + + jobject sparseArray = env->NewObject(gSparseArrayOffsets.classObject, + gSparseArrayOffsets.constructor); + const size_t N = res.getBasePackageCount(); + for (size_t i = 0; i < N; i++) { + const String16 name = res.getBasePackageName(i); + env->CallVoidMethod(sparseArray, gSparseArrayOffsets.put, (jint) res.getBasePackageId(i), + env->NewString(name, name.size())); + } + return sparseArray; +} + static jlong android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz) { AssetManager* am = assetManagerForJavaObject(env, clazz); @@ -1675,16 +1702,19 @@ static jlong android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobjec return 0; } - Asset* a = cookie - ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_BUFFER) - : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER); + int32_t assetCookie = static_cast<int32_t>(cookie); + Asset* a = assetCookie + ? am->openNonAsset(assetCookie, fileName8.c_str(), Asset::ACCESS_BUFFER) + : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER, &assetCookie); if (a == NULL) { jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); return 0; } - ResXMLTree* block = new ResXMLTree(); + const DynamicRefTable* dynamicRefTable = + am->getResources().getDynamicRefTableForCookie(assetCookie); + ResXMLTree* block = new ResXMLTree(dynamicRefTable); status_t err = block->setTo(a->getBuffer(true), a->getLength(), true); a->close(); delete a; @@ -1972,6 +2002,8 @@ static JNINativeMethod gAssetManagerMethods[] = { (void*) android_content_AssetManager_getNativeStringBlock }, { "getCookieName","(I)Ljava/lang/String;", (void*) android_content_AssetManager_getCookieName }, + { "getAssignedPackageIdentifiers","()Landroid/util/SparseArray;", + (void*) android_content_AssetManager_getAssignedPackageIdentifiers }, // Themes. { "newTheme", "()J", @@ -2068,6 +2100,16 @@ int register_android_content_AssetManager(JNIEnv* env) g_stringClass = (jclass)env->NewGlobalRef(stringClass); LOG_FATAL_IF(g_stringClass == NULL, "Unable to create global reference for class java/lang/String"); + jclass sparseArrayClass = env->FindClass("android/util/SparseArray"); + LOG_FATAL_IF(sparseArrayClass == NULL, "Unable to find class android/util/SparseArray"); + gSparseArrayOffsets.classObject = (jclass) env->NewGlobalRef(sparseArrayClass); + gSparseArrayOffsets.constructor = + env->GetMethodID(gSparseArrayOffsets.classObject, "<init>", "()V"); + LOG_FATAL_IF(gSparseArrayOffsets.constructor == NULL, "Unable to find SparseArray.<init>()"); + gSparseArrayOffsets.put = + env->GetMethodID(gSparseArrayOffsets.classObject, "put", "(ILjava/lang/Object;)V"); + LOG_FATAL_IF(gSparseArrayOffsets.put == NULL, "Unable to find SparseArray.put(int, V)"); + return AndroidRuntime::registerNativeMethods(env, "android/content/res/AssetManager", gAssetManagerMethods, NELEM(gAssetManagerMethods)); } diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp index 03de5c0..2cccb83 100644 --- a/core/jni/android_util_XmlBlock.cpp +++ b/core/jni/android_util_XmlBlock.cpp @@ -47,10 +47,11 @@ static jlong android_content_XmlBlock_nativeCreate(JNIEnv* env, jobject clazz, } jbyte* b = env->GetByteArrayElements(bArray, NULL); - ResXMLTree* osb = new ResXMLTree(b+off, len, true); + ResXMLTree* osb = new ResXMLTree(); + osb->setTo(b+off, len, true); env->ReleaseByteArrayElements(bArray, b, 0); - if (osb == NULL || osb->getError() != NO_ERROR) { + if (osb->getError() != NO_ERROR) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return 0; } @@ -113,6 +114,8 @@ static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz, return 1; case ResXMLParser::BAD_DOCUMENT: goto bad; + default: + break; } } while (true); diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h index a13dd16..610528c 100644 --- a/include/androidfw/AssetManager.h +++ b/include/androidfw/AssetManager.h @@ -161,7 +161,7 @@ public: * path hierarchy, and will not be seen by "AssetDir" or included * in our filename cache. */ - Asset* openNonAsset(const char* fileName, AccessMode mode); + Asset* openNonAsset(const char* fileName, AccessMode mode, int32_t* outCookie = NULL); /* * Explicit non-asset file. The file explicitly named by the cookie (the diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index b334aab..7cc10be 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -25,6 +25,7 @@ #include <utils/Errors.h> #include <utils/String16.h> #include <utils/Vector.h> +#include <utils/KeyedVector.h> #include <utils/threads.h> @@ -216,7 +217,8 @@ enum { // Chunk types in RES_TABLE_TYPE RES_TABLE_PACKAGE_TYPE = 0x0200, RES_TABLE_TYPE_TYPE = 0x0201, - RES_TABLE_TYPE_SPEC_TYPE = 0x0202 + RES_TABLE_TYPE_SPEC_TYPE = 0x0202, + RES_TABLE_LIBRARY_TYPE = 0x0203 }; /** @@ -268,6 +270,9 @@ struct Res_value // The 'data' holds a complex number encoding a fraction of a // container. TYPE_FRACTION = 0x06, + // The 'data' holds a dynamic ResTable_ref, which needs to be + // resolved before it can be used like a TYPE_REFERENCE. + TYPE_DYNAMIC_REFERENCE = 0x07, // Beginning of integer flavors... TYPE_FIRST_INT = 0x10, @@ -457,6 +462,7 @@ public: ResStringPool(const void* data, size_t size, bool copyData=false); ~ResStringPool(); + void setToEmpty(); status_t setTo(const void* data, size_t size, bool copyData=false); status_t getError() const; @@ -735,14 +741,16 @@ private: const void* mCurExt; }; +class DynamicRefTable; + /** * Convenience class for accessing data in a ResXMLTree resource. */ class ResXMLTree : public ResXMLParser { public: + ResXMLTree(const DynamicRefTable* dynamicRefTable); ResXMLTree(); - ResXMLTree(const void* data, size_t size, bool copyData=false); ~ResXMLTree(); status_t setTo(const void* data, size_t size, bool copyData=false); @@ -756,6 +764,8 @@ private: status_t validateNode(const ResXMLTree_node* node) const; + const DynamicRefTable* const mDynamicRefTable; + status_t mError; void* mOwnedData; const ResXMLTree_header* mHeader; @@ -1290,6 +1300,7 @@ struct ResTable_entry struct ResTable_map_entry : public ResTable_entry { // Resource identifier of the parent mapping, or 0 if there is none. + // This is always treated as a TYPE_DYNAMIC_REFERENCE. ResTable_ref parent; // Number of name/value pairs that follow for FLAG_COMPLEX. uint32_t count; @@ -1385,6 +1396,68 @@ struct ResTable_map }; /** + * A package-id to package name mapping for any shared libraries used + * in this resource table. The package-id's encoded in this resource + * table may be different than the id's assigned at runtime. We must + * be able to translate the package-id's based on the package name. + */ +struct ResTable_lib_header +{ + struct ResChunk_header header; + + // The number of shared libraries linked in this resource table. + uint32_t count; +}; + +/** + * A shared library package-id to package name entry. + */ +struct ResTable_lib_entry +{ + // The package-id this shared library was assigned at build time. + // We use a uint32 to keep the structure aligned on a uint32 boundary. + uint32_t packageId; + + // The package name of the shared library. \0 terminated. + char16_t packageName[128]; +}; + +/** + * Holds the shared library ID table. Shared libraries are assigned package IDs at + * build time, but they may be loaded in a different order, so we need to maintain + * a mapping of build-time package ID to run-time assigned package ID. + * + * Dynamic references are not currently supported in overlays. Only the base package + * may have dynamic references. + */ +class DynamicRefTable +{ +public: + DynamicRefTable(uint8_t packageId); + + // Loads an unmapped reference table from the package. + status_t load(const ResTable_lib_header* const header); + + // Creates a mapping from build-time package ID to run-time package ID for + // the given package. + status_t addMapping(const String16& packageName, uint8_t packageId); + + // Performs the actual conversion of build-time resource ID to run-time + // resource ID. + inline status_t lookupResourceId(uint32_t* resId) const; + inline status_t lookupResourceValue(Res_value* value) const; + + inline const KeyedVector<String16, uint8_t>& entries() const { + return mEntries; + } + +private: + const uint8_t mAssignedPackageId; + uint8_t mLookupTable[256]; + KeyedVector<String16, uint8_t> mEntries; +}; + +/** * Convenience class for accessing data in a ResTable resource. */ class ResTable @@ -1399,6 +1472,7 @@ public: const void* idmap = NULL); status_t add(const void *data, size_t size); status_t add(ResTable* src); + status_t addEmpty(const int32_t cookie); status_t getError() const; @@ -1634,7 +1708,7 @@ public: bool append = false); size_t getBasePackageCount() const; - const char16_t* getBasePackageName(size_t idx) const; + const String16 getBasePackageName(size_t idx) const; uint32_t getBasePackageId(size_t idx) const; // Return the number of resource tables that the object contains. @@ -1647,6 +1721,8 @@ public: // Return unique cookie identifier for the given resource table. int32_t getTableCookie(size_t index) const; + const DynamicRefTable* getDynamicRefTableForCookie(int32_t cookie) const; + // Return the configurations (ResTable_config) that we know about void getConfigurations(Vector<ResTable_config>* configs) const; @@ -1684,7 +1760,7 @@ private: struct bag_set; status_t addInternal(const void* data, size_t size, const int32_t cookie, - Asset* asset, bool copyData, const Asset* idmap); + bool copyData, const Asset* idmap); ssize_t getResourcePackageIndex(uint32_t resID) const; ssize_t getEntry( @@ -1712,6 +1788,8 @@ private: // Mapping from resource package IDs to indices into the internal // package array. uint8_t mPackageMap[256]; + + uint8_t mNextPackageId; }; } // namespace android diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 64363d4..91dda75 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -74,6 +74,7 @@ static const char* kAssetsRoot = "assets"; static const char* kAppZipName = NULL; //"classes.jar"; static const char* kSystemAssets = "framework/framework-res.apk"; static const char* kResourceCache = "resource-cache"; +static const char* kAndroidManifest = "AndroidManifest.xml"; static const char* kExcludeExtension = ".EXCLUDE"; @@ -205,6 +206,16 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) ALOGV("In %p Asset %s path: %s", this, ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); + // Check that the path has an AndroidManifest.xml + Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked( + kAndroidManifest, Asset::ACCESS_BUFFER, ap); + if (manifestAsset == NULL) { + // This asset path does not contain any resources. + delete manifestAsset; + return false; + } + delete manifestAsset; + mAssetPaths.add(ap); // new paths are always added at the end @@ -461,7 +472,7 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode) * The "fileName" is the partial path starting from the application * name. */ -Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) +Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t* outCookie) { AutoMutex _l(mLock); @@ -482,6 +493,7 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) Asset* pAsset = openNonAssetInPathLocked( fileName, mode, mAssetPaths.itemAt(i)); if (pAsset != NULL) { + if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1); return pAsset != kExcludedAsset ? pAsset : NULL; } } @@ -556,9 +568,14 @@ const ResTable* AssetManager::getResTable(bool required) const LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); } - if (mCacheMode != CACHE_OFF && !mCacheValid) + if (mCacheMode != CACHE_OFF && !mCacheValid) { const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); + } + + mResources = new ResTable(); + updateResourceParamsLocked(); + bool onlyEmptyResources = true; const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { Asset* ass = NULL; @@ -621,35 +638,39 @@ const ResTable* AssetManager::getResTable(bool required) const ap); shared = false; } + if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { - if (rt == NULL) { - mResources = rt = new ResTable(); - updateResourceParamsLocked(); - } ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); - rt->add(sharedRes); + mResources->add(sharedRes); } else { ALOGV("Parsing resources for %s", ap.path.string()); - rt->add(ass, i + 1, !shared, idmap); + mResources->add(ass, i + 1, !shared, idmap); } + onlyEmptyResources = false; if (!shared) { delete ass; } + } else { + ALOGW("Installing empty resources in to table %p\n", mResources); + mResources->addEmpty(i + 1); } + if (idmap != NULL) { delete idmap; } MY_TRACE_END(); } - if (required && !rt) ALOGW("Unable to find resources file resources.arsc"); - if (!rt) { - mResources = rt = new ResTable(); + if (required && onlyEmptyResources) { + ALOGW("Unable to find resources file resources.arsc"); + delete mResources; + mResources = NULL; } - return rt; + + return mResources; } void AssetManager::updateResourceParamsLocked() const diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 652cd4a..04ca81e 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -42,6 +42,7 @@ #define TABLE_SUPER_NOISY(x) //x #define LOAD_TABLE_NOISY(x) //x #define TABLE_THEME(x) //x +#define LIB_NOISY(x) x namespace android { @@ -66,6 +67,9 @@ namespace android { // size measured in sizeof(uint32_t) #define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t)) +#define APP_PACKAGE_ID 0x7f +#define SYS_PACKAGE_ID 0x01 + // Standard C isspace() is only required to look at the low byte of its input, so // produces incorrect results for UTF-16 characters. For safety's sake, assume that // any high-byte UTF-16 code point is not whitespace. @@ -113,7 +117,7 @@ static status_t validate_chunk(const ResChunk_header* chunk, name, size, headerSize); return BAD_TYPE; } - ALOGW("%s header size 0x%x is too small.", + ALOGW("%s header size 0x%04x is too small.", name, headerSize); return BAD_TYPE; } @@ -264,7 +268,7 @@ static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, } const uint32_t index = typeOffset + 2 + entry - entryOffset; if (index > size) { - ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, (int)size); + ALOGW("Resource ID map: entry index=%u exceeds size of map=%d\n", index, (int)size); *outValue = 0; return NO_ERROR; } @@ -279,7 +283,7 @@ static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t return UNKNOWN_ERROR; } if (mapSize <= IDMAP_HEADER_SIZE + 1) { - ALOGW("corrupt idmap: map size %d too short\n", mapSize); + ALOGW("corrupt idmap: map size %d too short\n", (int)mapSize); return UNKNOWN_ERROR; } uint32_t typeCount = *(map + IDMAP_HEADER_SIZE); @@ -288,7 +292,7 @@ static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t return UNKNOWN_ERROR; } if (IDMAP_HEADER_SIZE + 1 + typeCount > mapSize) { - ALOGW("corrupt idmap: number of types %d extends past idmap size %d\n", typeCount, mapSize); + ALOGW("corrupt idmap: number of types %u extends past idmap size %d\n", typeCount, (int)mapSize); return UNKNOWN_ERROR; } const uint32_t* p = map + IDMAP_HEADER_SIZE + 1; @@ -304,7 +308,7 @@ static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t // determine package id from first entry of first type const uint32_t offset = *p + IDMAP_HEADER_SIZE + 2; if (offset > mapSize) { - ALOGW("corrupt idmap: entry offset %d points outside map size %d\n", offset, mapSize); + ALOGW("corrupt idmap: entry offset %u points outside map size %d\n", offset, (int)mapSize); return UNKNOWN_ERROR; } *outId = (map[offset] >> 24) & 0x000000ff; @@ -342,6 +346,22 @@ ResStringPool::~ResStringPool() uninit(); } +void ResStringPool::setToEmpty() +{ + uninit(); + + mOwnedData = calloc(1, sizeof(ResStringPool_header)); + ResStringPool_header* header = (ResStringPool_header*) mOwnedData; + mSize = 0; + mEntries = NULL; + mStrings = NULL; + mStringPoolSize = 0; + mEntryStyles = NULL; + mStyles = NULL; + mStylePoolSize = 0; + mHeader = (const ResStringPool_header*) header; +} + status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) { if (!data || !size) { @@ -1111,7 +1131,14 @@ int32_t ResXMLParser::getAttributeDataType(size_t idx) const (((const uint8_t*)tag) + dtohs(tag->attributeStart) + (dtohs(tag->attributeSize)*idx)); - return attr->typedValue.dataType; + uint8_t type = attr->typedValue.dataType; + if (type != Res_value::TYPE_DYNAMIC_REFERENCE) { + return type; + } + + // This is a dynamic reference. We adjust those references + // to regular references at this level, so lie to the caller. + return Res_value::TYPE_REFERENCE; } } return Res_value::TYPE_NULL; @@ -1126,7 +1153,15 @@ int32_t ResXMLParser::getAttributeData(size_t idx) const (((const uint8_t*)tag) + dtohs(tag->attributeStart) + (dtohs(tag->attributeSize)*idx)); - return dtohl(attr->typedValue.data); + if (attr->typedValue.dataType != Res_value::TYPE_DYNAMIC_REFERENCE || + mTree.mDynamicRefTable == NULL) { + return dtohl(attr->typedValue.data); + } + + uint32_t data = dtohl(attr->typedValue.data); + if (mTree.mDynamicRefTable->lookupResourceId(&data) == NO_ERROR) { + return data; + } } } return 0; @@ -1142,6 +1177,10 @@ ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const + dtohs(tag->attributeStart) + (dtohs(tag->attributeSize)*idx)); outValue->copyFrom_dtoh(attr->typedValue); + if (mTree.mDynamicRefTable != NULL && + mTree.mDynamicRefTable->lookupResourceValue(outValue) != NO_ERROR) { + return BAD_TYPE; + } return sizeof(Res_value); } } @@ -1333,25 +1372,26 @@ void ResXMLParser::setPosition(const ResXMLParser::ResXMLPosition& pos) mCurExt = pos.curExt; } - // -------------------------------------------------------------------- static volatile int32_t gCount = 0; -ResXMLTree::ResXMLTree() +ResXMLTree::ResXMLTree(const DynamicRefTable* dynamicRefTable) : ResXMLParser(*this) + , mDynamicRefTable(dynamicRefTable) , mError(NO_INIT), mOwnedData(NULL) { //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1); restart(); } -ResXMLTree::ResXMLTree(const void* data, size_t size, bool copyData) +ResXMLTree::ResXMLTree() : ResXMLParser(*this) + , mDynamicRefTable(NULL) , mError(NO_INIT), mOwnedData(NULL) { //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1); - setTo(data, size, copyData); + restart(); } ResXMLTree::~ResXMLTree() @@ -2737,7 +2777,14 @@ struct ResTable::Package struct ResTable::PackageGroup { PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id) - : owner(_owner), name(_name), id(_id), typeCount(0), bags(NULL) { } + : owner(_owner) + , name(_name) + , id(_id) + , typeCount(0) + , bags(NULL) + , dynamicRefTable(static_cast<uint8_t>(_id)) + { } + ~PackageGroup() { clearBagCache(); const size_t N = packages.size(); @@ -2790,6 +2837,13 @@ struct ResTable::PackageGroup // Computed attribute bags, first indexed by the type and second // by the entry in that type. bag_set*** bags; + + // The table mapping dynamic references to resolved references for + // this package group. + // TODO: We may be able to support dynamic references in overlays + // by having these tables in a per-package scope rather than + // per-package-group. + DynamicRefTable dynamicRefTable; }; struct ResTable::bag_set @@ -3077,7 +3131,7 @@ void ResTable::Theme::dumpToLog() const } ResTable::ResTable() - : mError(NO_INIT) + : mError(NO_INIT), mNextPackageId(2) { memset(&mParams, 0, sizeof(mParams)); memset(mPackageMap, 0, sizeof(mPackageMap)); @@ -3085,11 +3139,11 @@ ResTable::ResTable() } ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool copyData) - : mError(NO_INIT) + : mError(NO_INIT), mNextPackageId(2) { memset(&mParams, 0, sizeof(mParams)); memset(mPackageMap, 0, sizeof(mPackageMap)); - addInternal(data, size, cookie, NULL /* asset */, copyData, NULL /* idMap */); + addInternal(data, size, cookie, copyData, NULL /* idMap */); LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table"); //ALOGI("Creating ResTable %p\n", this); } @@ -3106,7 +3160,7 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const } status_t ResTable::add(const void* data, size_t size) { - return addInternal(data, size, 0 /* cookie */, NULL /* asset */, + return addInternal(data, size, 0 /* cookie */, false /* copyData */, NULL /* idMap */); } @@ -3118,7 +3172,7 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData, const return UNKNOWN_ERROR; } size_t size = (size_t)asset->getLength(); - return addInternal(data, size, cookie, asset, copyData, + return addInternal(data, size, cookie, copyData, reinterpret_cast<const Asset*>(idmap)); } @@ -3146,8 +3200,25 @@ status_t ResTable::add(ResTable* src) return mError; } +status_t ResTable::addEmpty(const int32_t cookie) { + Header* header = new Header(this); + header->index = mHeaders.size(); + header->cookie = cookie; + header->values.setToEmpty(); + header->ownedData = calloc(1, sizeof(ResTable_header)); + + ResTable_header* resHeader = (ResTable_header*) header->ownedData; + resHeader->header.type = RES_TABLE_TYPE; + resHeader->header.headerSize = sizeof(ResTable_header); + resHeader->header.size = sizeof(ResTable_header); + + header->header = (const ResTable_header*) resHeader; + mHeaders.add(header); + return NO_ERROR; +} + status_t ResTable::addInternal(const void* data, size_t size, const int32_t cookie, - Asset* /*asset*/, bool copyData, const Asset* idmap) + bool copyData, const Asset* idmap) { if (!data) return NO_ERROR; Header* header = new Header(this); @@ -3186,8 +3257,6 @@ status_t ResTable::addInternal(const void* data, size_t size, const int32_t cook //ALOGI("Got size 0x%x, again size 0x%x, raw size 0x%x\n", header->size, // dtohl(header->header->header.size), header->header->header.size); LOAD_TABLE_NOISY(ALOGV("Loading ResTable @%p:\n", header->header)); - LOAD_TABLE_NOISY(printHexData(2, header->header, header->size < 256 ? header->size : 256, - 16, 16, 0, false, printToLogFunc)); if (dtohs(header->header->header.headerSize) > header->size || header->size > size) { ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n", @@ -3474,9 +3543,6 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag continue; } - TABLE_NOISY(aout << "Resource type data: " - << HexDump(type, dtohl(type->header.size)) << endl); - if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) { ALOGW("ResTable_item at %d is beyond type chunk data %d", (int)offset, dtohl(type->header.size)); @@ -3516,6 +3582,18 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag outValue->res0 = bestValue->res0; outValue->dataType = bestValue->dataType; outValue->data = dtohl(bestValue->data); + + // The reference may be pointing to a resource in a shared library. These + // references have build-time generated package IDs. These ids may not match + // the actual package IDs of the corresponding packages in this ResTable. + // We need to fix the package ID based on a mapping. + status_t err = grp->dynamicRefTable.lookupResourceValue(outValue); + if (err != NO_ERROR) { + ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data); + rc = BAD_VALUE; + goto out; + } + if (outConfig != NULL) { *outConfig = bestItem; } @@ -3545,8 +3623,8 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, ResTable_config* outConfig) const { int count=0; - while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE - && value->data != 0 && count < 20) { + while (blockIndex >= 0 && value->dataType == Res_value::TYPE_REFERENCE + && value->data != 0 && count < 20) { if (outLastRef) *outLastRef = value->data; uint32_t lastRef = value->data; uint32_t newFlags = 0; @@ -3730,7 +3808,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, const Type* typeClass; ALOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E); ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass); - ALOGV("Resulting offset=%d\n", offset); + ALOGV("Resulting offset=%d\n", (int)offset); if (offset <= 0) { // No {entry, appropriate config} pair found in package. If this // package is an overlay package (ip != 0), this simply means the @@ -3774,9 +3852,21 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n", entrySize, parent)); if (parent) { + uint32_t resolvedParent = parent; + + // Bags encode a parent reference without using the standard + // Res_value structure. That means we must always try to + // resolve a parent reference in case it is actually a + // TYPE_DYNAMIC_REFERENCE. + status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + ALOGE("Failed resolving bag parent id 0x%08x", parent); + return UNKNOWN_ERROR; + } + const bag_entry* parentBag; uint32_t parentTypeSpecFlags = 0; - const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags); + const ssize_t NP = getBagLocked(resolvedParent, &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) { @@ -3871,6 +3961,12 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, cur->stringBlock = package->header->index; cur->map.name.ident = newName; cur->map.value.copyFrom_dtoh(map->value); + status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value); + if (err != NO_ERROR) { + ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data); + return UNKNOWN_ERROR; + } + TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n", curEntry, cur, cur->stringBlock, cur->map.name.ident, cur->map.value.dataType, cur->map.value.data)); @@ -4568,16 +4664,20 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, return false; } } - if (!accessor) { - outValue->data = rid; - return true; + + if (accessor) { + rid = Res_MAKEID( + accessor->getRemappedPackage(Res_GETPACKAGE(rid)), + Res_GETTYPE(rid), Res_GETENTRY(rid)); + TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n", + String8(package).string(), String8(type).string(), + String8(name).string(), rid)); + } + + uint32_t packageId = Res_GETPACKAGE(rid) + 1; + if (packageId != APP_PACKAGE_ID && packageId != SYS_PACKAGE_ID) { + outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE; } - rid = Res_MAKEID( - accessor->getRemappedPackage(Res_GETPACKAGE(rid)), - Res_GETTYPE(rid), Res_GETENTRY(rid)); - TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n", - String8(package).string(), String8(type).string(), - String8(name).string(), rid)); outValue->data = rid; return true; } @@ -4589,8 +4689,17 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, TABLE_NOISY(printf("Pckg %s:%s/%s: 0x%08x\n", String8(package).string(), String8(type).string(), String8(name).string(), rid)); - outValue->data = rid; - return true; + uint32_t packageId = Res_GETPACKAGE(rid) + 1; + if (packageId == 0x00) { + outValue->data = rid; + outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE; + return true; + } else if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID) { + // We accept packageId's generated as 0x01 in order to support + // building the android system resources + outValue->data = rid; + return true; + } } } } @@ -5122,15 +5231,15 @@ size_t ResTable::getBasePackageCount() const return mPackageGroups.size(); } -const char16_t* ResTable::getBasePackageName(size_t idx) const +const String16 ResTable::getBasePackageName(size_t idx) const { if (mError != NO_ERROR) { - return 0; + return String16(); } LOG_FATAL_IF(idx >= mPackageGroups.size(), "Requested package index %d past package count %d", (int)idx, (int)mPackageGroups.size()); - return mPackageGroups[idx]->name.string(); + return mPackageGroups[idx]->name; } uint32_t ResTable::getBasePackageId(size_t idx) const @@ -5159,6 +5268,21 @@ int32_t ResTable::getTableCookie(size_t index) const return mHeaders[index]->cookie; } +const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) const +{ + const size_t N = mPackageGroups.size(); + for (size_t i = 0; i < N; i++) { + const PackageGroup* pg = mPackageGroups[i]; + size_t M = pg->packages.size(); + for (size_t j = 0; j < M; j++) { + if (pg->packages[j]->header->cookie == cookie) { + return &pg->dynamicRefTable; + } + } + } + return NULL; +} + void ResTable::getConfigurations(Vector<ResTable_config>* configs) const { const size_t I = mPackageGroups.size(); @@ -5300,10 +5424,9 @@ ssize_t ResTable::getEntry( } offset += dtohl(type->entriesStart); - TABLE_NOISY(aout << "Looking in resource table " << package->header->header - << ", typeOff=" - << (void*)(((const char*)type)-((const char*)package->header->header)) - << ", offset=" << (void*)offset << endl); + TABLE_NOISY(ALOGD("Looking in resource table %p, typeOff=%p, offset=%p", + package->header->header, (void*)(((const char*)type)-((const char*)package->header->header)), + (void*)offset)); if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) { ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x", @@ -5377,6 +5500,11 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (package == NULL) { return (mError=NO_MEMORY); } + + if (id == 0) { + // This is a library so assign an ID + id = mNextPackageId++; + } size_t idx = mPackageMap[id]; if (idx == 0) { @@ -5413,6 +5541,13 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, group->basePackage = package; mPackageMap[id] = (uint8_t)idx; + + // Find all packages that reference this package + size_t N = mPackageGroups.size(); + for (size_t i = 0; i < N; i++) { + mPackageGroups[i]->dynamicRefTable.addMapping( + group->name, static_cast<uint8_t>(group->id)); + } } else { group = mPackageGroups.itemAt(idx-1); if (group == NULL) { @@ -5457,7 +5592,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, (void*)(base-(const uint8_t*)chunk), dtohs(typeSpec->header.type), dtohs(typeSpec->header.headerSize), - (void*)typeSize)); + (void*)typeSpecSize)); // look for block overrun or int overflow when multiplying by 4 if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t)) || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount)) @@ -5543,6 +5678,21 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, ALOGI("Adding config to type %d: %s\n", type->id, thisConfig.toString().string())); t->configs.add(type); + } else if (ctype == RES_TABLE_LIBRARY_TYPE) { + if (group->dynamicRefTable.entries().size() == 0) { + status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk); + if (err != NO_ERROR) { + return (mError=err); + } + + // Fill in the reference table with the entries we already know about. + size_t N = mPackageGroups.size(); + for (size_t i = 0; i < N; i++) { + group->dynamicRefTable.addMapping(mPackageGroups[i]->name, mPackageGroups[i]->id); + } + } else { + ALOGW("Found multiple library tables, ignoring..."); + } } else { status_t err = validate_chunk(chunk, sizeof(ResChunk_header), endPos, "ResTable_package:unknown"); @@ -5561,6 +5711,103 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return NO_ERROR; } +DynamicRefTable::DynamicRefTable(uint8_t packageId) + : mAssignedPackageId(packageId) +{ + memset(mLookupTable, 0, sizeof(mLookupTable)); + + // Reserved package ids + mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID; + mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID; +} + +status_t DynamicRefTable::load(const ResTable_lib_header* const header) +{ + const uint32_t entryCount = dtohl(header->count); + const uint32_t sizeOfEntries = sizeof(ResTable_lib_entry) * entryCount; + const uint32_t expectedSize = dtohl(header->header.size) - dtohl(header->header.headerSize); + if (sizeOfEntries > expectedSize) { + ALOGE("ResTable_lib_header size %u is too small to fit %u entries (x %u).", + expectedSize, entryCount, (uint32_t)sizeof(ResTable_lib_entry)); + return UNKNOWN_ERROR; + } + + const ResTable_lib_entry* entry = (const ResTable_lib_entry*)(((uint8_t*) header) + + dtohl(header->header.headerSize)); + for (uint32_t entryIndex = 0; entryIndex < entryCount; entryIndex++) { + uint32_t packageId = dtohl(entry->packageId); + char16_t tmpName[sizeof(entry->packageName) / sizeof(char16_t)]; + strcpy16_dtoh(tmpName, entry->packageName, sizeof(entry->packageName) / sizeof(char16_t)); + LIB_NOISY(ALOGV("Found lib entry %s with id %d\n", String8(tmpName).string(), + dtohl(entry->packageId))); + if (packageId >= 256) { + ALOGE("Bad package id 0x%08x", packageId); + return UNKNOWN_ERROR; + } + mEntries.replaceValueFor(String16(tmpName), (uint8_t) packageId); + entry = entry + 1; + } + return NO_ERROR; +} + +status_t DynamicRefTable::addMapping(const String16& packageName, uint8_t packageId) +{ + ssize_t index = mEntries.indexOfKey(packageName); + if (index < 0) { + return UNKNOWN_ERROR; + } + mLookupTable[mEntries.valueAt(index)] = packageId; + return NO_ERROR; +} + +status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { + uint32_t res = *resId; + size_t packageId = Res_GETPACKAGE(res) + 1; + + if (packageId == APP_PACKAGE_ID) { + // No lookup needs to be done, app package IDs are absolute. + return NO_ERROR; + } + + if (packageId == 0) { + // The package ID is 0x00. That means that a shared library is accessing + // its own local resource, so we fix up the resource with the calling + // package ID. + *resId |= ((uint32_t) mAssignedPackageId) << 24; + return NO_ERROR; + } + + // Do a proper lookup. + uint8_t translatedId = mLookupTable[packageId]; + if (translatedId == 0) { + ALOGV("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.", + (uint8_t)mAssignedPackageId, (uint8_t)packageId); + for (size_t i = 0; i < 256; i++) { + if (mLookupTable[i] != 0) { + ALOGV("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]); + } + } + return UNKNOWN_ERROR; + } + + *resId = (res & 0x00ffffff) | (((uint32_t) translatedId) << 24); + return NO_ERROR; +} + +status_t DynamicRefTable::lookupResourceValue(Res_value* value) const { + if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE) { + return NO_ERROR; + } + + status_t err = lookupResourceId(&value->data); + if (err != NO_ERROR) { + return err; + } + + value->dataType = Res_value::TYPE_REFERENCE; + return NO_ERROR; +} + status_t ResTable::createIdmap(const ResTable& overlay, uint32_t targetCrc, uint32_t overlayCrc, const char* targetPath, const char* overlayPath, @@ -5701,7 +5948,7 @@ status_t ResTable::createIdmap(const ResTable& overlay, continue; } if (N == 1) { // vector expected to hold (offset) + (N > 0 entries) - ALOGW("idmap: type %d supposedly has entries, but no entries found\n", i); + ALOGW("idmap: type %u supposedly has entries, but no entries found\n", (uint32_t)i); return UNKNOWN_ERROR; } *data++ = htodl(N - 1); // do not count the offset (which is vector's first element) @@ -5815,6 +6062,8 @@ void ResTable::print_value(const Package* pkg, const Res_value& value) const printf("(null)\n"); } else if (value.dataType == Res_value::TYPE_REFERENCE) { printf("(reference) 0x%08x\n", value.data); + } else if (value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE) { + printf("(dynamic reference) 0x%08x\n", value.data); } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) { printf("(attribute) 0x%08x\n", value.data); } else if (value.dataType == Res_value::TYPE_STRING) { @@ -5897,6 +6146,11 @@ void ResTable::print(bool inclValues) const uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) | (0x00ff0000 & ((typeIndex+1)<<16)) | (0x0000ffff & (entryIndex)); + // Since we are creating resID without actually + // iterating over them, we have no idea which is a + // dynamic reference. We must check. + pg->dynamicRefTable.lookupResourceId(&resID); + resource_name resName; if (this->getResourceName(resID, true, &resName)) { String8 type8; @@ -5956,6 +6210,7 @@ void ResTable::print(bool inclValues) const uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) | (0x00ff0000 & ((typeIndex+1)<<16)) | (0x0000ffff & (entryIndex)); + pg->dynamicRefTable.lookupResourceId(&resID); resource_name resName; if (this->getResourceName(resID, true, &resName)) { String8 type8; @@ -6034,8 +6289,14 @@ void ResTable::print(bool inclValues) const const uint8_t* baseMapPtr = (const uint8_t*)ent; size_t mapOffset = esize; const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); - printf(" Parent=0x%08x, Count=%d\n", - dtohl(bagPtr->parent.ident), N); + const uint32_t parent = dtohl(bagPtr->parent.ident); + uint32_t resolvedParent = parent; + status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + resolvedParent = 0; + } + printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", + parent, resolvedParent, N); for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { printf(" #%i (Key=0x%08x): ", i, dtohl(mapPtr->name.ident)); diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp index 2c9f650..7a4dd13 100644 --- a/libs/androidfw/tests/ObbFile_test.cpp +++ b/libs/androidfw/tests/ObbFile_test.cpp @@ -91,7 +91,7 @@ TEST_F(ObbFileTest, WriteThenRead) { EXPECT_EQ(sizeof(salt), saltLen) << "salt sizes were not the same"; - for (int i = 0; i < sizeof(salt); i++) { + for (size_t i = 0; i < sizeof(salt); i++) { EXPECT_EQ(salt[i], newSalt[i]) << "salt character " << i << " should be equal"; } diff --git a/tests/SharedLibrary/client/AndroidManifest.xml b/tests/SharedLibrary/client/AndroidManifest.xml index c6a43c0..a39c754 100644 --- a/tests/SharedLibrary/client/AndroidManifest.xml +++ b/tests/SharedLibrary/client/AndroidManifest.xml @@ -16,7 +16,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.google.android.test.lib_client"> - <application android:label="@string/app_title"> + <application android:label="@string/app_title" android:theme="@style/Theme"> <uses-library android:name="android.test.runner" /> <uses-library android:name="com.google.android.test.shared_library" /> <activity android:name="ActivityMain"> diff --git a/tests/SharedLibrary/client/res/layout/main.xml b/tests/SharedLibrary/client/res/layout/main.xml new file mode 100644 index 0000000..067ef9f --- /dev/null +++ b/tests/SharedLibrary/client/res/layout/main.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView android:id="@+id/label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@com.google.android.test.shared_library:string/shared_string" + style="@com.google.android.test.shared_library:style/CodeFont"/> + + <com.google.android.test.shared_library.AddressView + xmlns:custom="http://schemas.android.com/apk/res/com.google.android.test.shared_library" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + custom:name="Professor Android" + custom:streetNumber="44" + custom:streetName="KitKat Lane" + custom:city="AndroidVille" + custom:state="OS" + custom:country="Mobile" + custom:zip="12345"/> +</LinearLayout> diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml index 3757a25..d9efdc7 100644 --- a/tests/SharedLibrary/client/res/values/strings.xml +++ b/tests/SharedLibrary/client/res/values/strings.xml @@ -16,4 +16,5 @@ <resources> <string name="app_title">SharedLibrary client</string> + <string name="changes">@com.google.android.test.shared_library:string/shared_string</string> </resources> diff --git a/tests/SharedLibrary/client/res/values/themes.xml b/tests/SharedLibrary/client/res/values/themes.xml new file mode 100644 index 0000000..a14f98a --- /dev/null +++ b/tests/SharedLibrary/client/res/values/themes.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> + <style name="Theme" parent="com.google.android.test.shared_library:Theme"> + </style> +</resources> diff --git a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java index d6121a5..7276b3c 100644 --- a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java +++ b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java @@ -18,18 +18,33 @@ package com.google.android.test.lib_client; import android.app.Activity; import android.os.Bundle; -import android.widget.TextView; import com.google.android.test.shared_library.SharedLibraryMain; public class ActivityMain extends Activity { + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + String[] expectedAnimals = new String[] { + "Racoon", + "Rhino", + "Elephant" + }; + + String[] animals = getResources().getStringArray(com.google.android.test.shared_library.R.array.animals); + if (animals == null || animals.length != expectedAnimals.length) { + throw new AssertionError("Animal list from shared library is null or wrong length."); + } - TextView content = new TextView(this); - content.setText("Library version: " + SharedLibraryMain.getVersion(this) + "!"); + for (int i = 0; i < expectedAnimals.length; i++) { + if (!expectedAnimals[i].equals(animals[i])) { + throw new AssertionError("Expected '" + expectedAnimals[i] + + "' at index " + i + " but got '" + animals[i]); + } + } SharedLibraryMain.ensureVersion(this, SharedLibraryMain.VERSION_BASE); - setContentView(content); } } diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk index c19e23a..b2fc369 100644 --- a/tests/SharedLibrary/lib/Android.mk +++ b/tests/SharedLibrary/lib/Android.mk @@ -3,8 +3,13 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_AAPT_FLAGS := --shared-lib LOCAL_PACKAGE_NAME := SharedLibrary -LOCAL_MODULE_TAGS := tests +LOCAL_EXPORT_PACKAGE_RESOURCES := true +LOCAL_PRIVILEGED_MODULE := true +LOCAL_MODULE_TAGS := optional + +LOCAL_PROGUARD_FLAG_FILES := proguard.proguard include $(BUILD_PACKAGE) diff --git a/tests/SharedLibrary/lib/proguard.proguard b/tests/SharedLibrary/lib/proguard.proguard new file mode 100644 index 0000000..e5dfbe1 --- /dev/null +++ b/tests/SharedLibrary/lib/proguard.proguard @@ -0,0 +1,7 @@ +-keepparameternames +-keepattributes Exceptions,InnerClasses,Signature,Deprecated, + SourceFile,LineNumberTable,*Annotation*,EnclosingMethod + +-keep public class * { + public protected *; +} diff --git a/tests/SharedLibrary/lib/res/layout/address.xml b/tests/SharedLibrary/lib/res/layout/address.xml new file mode 100644 index 0000000..835f43e --- /dev/null +++ b/tests/SharedLibrary/lib/res/layout/address.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <TextView android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/street" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/cityStateZip" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/country" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</merge> diff --git a/tests/SharedLibrary/lib/res/values/attrs.xml b/tests/SharedLibrary/lib/res/values/attrs.xml new file mode 100644 index 0000000..8cefe92 --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/attrs.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> + <declare-styleable name="AddressView"> + <attr name="name" format="string" /> + <attr name="streetNumber" format="integer" /> + <attr name="streetName" format="string" /> + <attr name="city" format="string" /> + <attr name="state" format="string" /> + <attr name="zip" format="string" /> + <attr name="country" format="string" /> + </declare-styleable> +</resources> diff --git a/tests/SharedLibrary/lib/res/values/public.xml b/tests/SharedLibrary/lib/res/values/public.xml new file mode 100644 index 0000000..37b1ec9 --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/public.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> + <public type="string" name="shared_string" id="0x00020003" /> + <public type="style" name="CodeFont" id="0x00040000" /> + <public type="style" name="Theme" id="0x00040001" /> + + <public type="attr" name="name" id="0x00010000" /> + <public type="attr" name="streetNumber" id="0x00010001" /> + <public type="attr" name="streetName" id="0x00010002" /> + <public type="attr" name="city" id="0x00010003" /> + <public type="attr" name="state" id="0x00010004" /> + <public type="attr" name="zip" id="0x00010005" /> + <public type="attr" name="country" id="0x00010006" /> + + <public type="array" name="animals" id="0x02050000" /> +</resources> diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml index bbfb0b4..6827f93 100644 --- a/tests/SharedLibrary/lib/res/values/strings.xml +++ b/tests/SharedLibrary/lib/res/values/strings.xml @@ -19,4 +19,13 @@ <string name="upgrade_body"><xliff:g id="app">%1$s</xliff:g> requires a newer version of <xliff:g id="lib">%2$s</xliff:g> to run.</string> <string name="upgrade_button">Upgrade</string> + <string name="shared_string">Shared string!</string> + + <string-array name="animals"> + <item>@string/racoon</item> + <item>Rhino</item> + <item>Elephant</item> + </string-array> + + <string name="racoon">Racoon</string> </resources> diff --git a/tests/SharedLibrary/lib/res/values/themes.xml b/tests/SharedLibrary/lib/res/values/themes.xml new file mode 100644 index 0000000..f1081ac --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/themes.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> + <style name="CodeFont" parent="@android:style/TextAppearance.Medium"> + <item name="android:textColor">#00FF00</item> + <item name="android:typeface">monospace</item> + </style> + + <style name="Theme" parent="android:Theme.Holo.Light"> + <item name="android:actionBarStyle">@style/ActionBar</item> + </style> + + <style name="ActionBar" parent="android:Widget.Holo.Light.ActionBar.Solid.Inverse"> + <item name="android:background">@color/orange</item> + </style> + + <color name="orange">#f0ad4e</color> +</resources> diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/AddressView.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/AddressView.java new file mode 100644 index 0000000..dcaf68c --- /dev/null +++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/AddressView.java @@ -0,0 +1,44 @@ +package com.google.android.test.shared_library; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class AddressView extends LinearLayout { + private TextView mNameView; + private TextView mStreetView; + private TextView mCityStateZipView; + private TextView mCountryView; + + public AddressView(Context context, AttributeSet attrs) { + super(context, attrs); + setOrientation(VERTICAL); + + View view = LayoutInflater.from(context).inflate(R.layout.address, this); + mNameView = (TextView) view.findViewById(R.id.name); + mStreetView = (TextView) view.findViewById(R.id.street); + mCityStateZipView = (TextView) view.findViewById(R.id.cityStateZip); + mCountryView = (TextView) view.findViewById(R.id.country); + + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.AddressView, + 0, 0); + try { + mNameView.setText(a.getString(R.styleable.AddressView_name)); + int streetNumber = a.getInteger(R.styleable.AddressView_streetNumber, -1); + mStreetView.setText((streetNumber <= 0 ? "" : Integer.toString(streetNumber)) + + " " + a.getString(R.styleable.AddressView_streetName)); + mCityStateZipView.setText(a.getString(R.styleable.AddressView_city) + ", " + + a.getString(R.styleable.AddressView_state) + " " + + a.getString(R.styleable.AddressView_zip)); + mCountryView.setText(a.getString(R.styleable.AddressView_country)); + } finally { + a.recycle(); + } + } +} diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index f9a2d19..38bfa00 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1798,6 +1798,11 @@ void* AaptFile::editData(size_t size) return buf; } +void* AaptFile::editDataInRange(size_t offset, size_t size) +{ + return (void*)(((uint8_t*) editData(offset + size)) + offset); +} + void* AaptFile::editData(size_t* outSize) { if (outSize) { diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 336d08b..82dda5f 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -251,6 +251,7 @@ public: size_t getSize() const { return mDataSize; } void* editData(size_t size); void* editData(size_t* outSize = NULL); + void* editDataInRange(size_t offset, size_t size); void* padData(size_t wordSize); status_t writeData(const void* data, size_t size); void clearData(); diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index 49b8b55..d2eccbe 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -64,6 +64,7 @@ public: mProduct(NULL), mUseCrunchCache(false), mErrorOnFailedInsert(false), mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL), mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), + mBuildSharedLibrary(false), mArgc(0), mArgv(NULL) {} ~Bundle(void) {} @@ -188,6 +189,8 @@ public: void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; } const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; } void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; } + bool getBuildSharedLibrary() const { return mBuildSharedLibrary; } + void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; } /* * Set and get the file specification. @@ -300,6 +303,7 @@ private: const char* mOutputTextSymbols; const char* mSingleCrunchInputFile; const char* mSingleCrunchOutputFile; + bool mBuildSharedLibrary; /* file specification */ int mArgc; diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 33711fa..1cf4783 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -190,6 +190,9 @@ void usage(void) " Make the resources ID non constant. This is required to make an R java class\n" " that does not contain the final value but is used to make reusable compiled\n" " libraries that need to access resources.\n" + " --shared-lib\n" + " Make a shared library resource package that can be loaded by an application\n" + " at runtime to access the libraries resources. Implies --non-constant-id.\n" " --error-on-failed-insert\n" " Forces aapt to return an error if it fails to insert values into the manifest\n" " with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n" @@ -618,6 +621,9 @@ int main(int argc, char* const argv[]) bundle.setProduct(argv[0]); } else if (strcmp(cp, "-non-constant-id") == 0) { bundle.setNonConstantId(true); + } else if (strcmp(cp, "-shared-lib") == 0) { + bundle.setNonConstantId(true); + bundle.setBuildSharedLibrary(true); } else if (strcmp(cp, "-no-crunch") == 0) { bundle.setUseCrunchCache(true); } else if (strcmp(cp, "-ignore-assets") == 0) { diff --git a/tools/aapt/ResourceIdCache.cpp b/tools/aapt/ResourceIdCache.cpp index e03f4f6..d60a07f 100644 --- a/tools/aapt/ResourceIdCache.cpp +++ b/tools/aapt/ResourceIdCache.cpp @@ -98,10 +98,10 @@ uint32_t ResourceIdCache::store(const android::String16& package, void ResourceIdCache::dump() { printf("ResourceIdCache dump:\n"); - printf("Size: %ld\n", mIdMap.size()); - printf("Hits: %ld\n", mHits); - printf("Misses: %ld\n", mMisses); - printf("(Collisions: %ld)\n", mCollisions); + printf("Size: %zd\n", mIdMap.size()); + printf("Hits: %zd\n", mHits); + printf("Misses: %zd\n", mMisses); + printf("(Collisions: %zd)\n", mCollisions); } } diff --git a/tools/aapt/ResourceIdCache.h b/tools/aapt/ResourceIdCache.h index e6bcda2..3acdee1 100644 --- a/tools/aapt/ResourceIdCache.h +++ b/tools/aapt/ResourceIdCache.h @@ -6,18 +6,20 @@ #ifndef RESOURCE_ID_CACHE_H #define RESOURCE_ID_CACHE_H +#include <utils/String16.h> + namespace android { class ResourceIdCache { public: - static uint32_t lookup(const android::String16& package, - const android::String16& type, - const android::String16& name, + static uint32_t lookup(const String16& package, + const String16& type, + const String16& name, bool onlyPublic); - static uint32_t store(const android::String16& package, - const android::String16& type, - const android::String16& name, + static uint32_t store(const String16& package, + const String16& type, + const String16& name, bool onlyPublic, uint32_t resId); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 652998e..0fb2606 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -1673,7 +1673,7 @@ status_t compileResourceFile(Bundle* bundle, ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage) : mAssetsPackage(assetsPackage), mNextPackageId(1), mHaveAppPackage(false), - mIsAppPackage(!bundle->getExtending()), + mIsAppPackage(!bundle->getExtending()), mIsSharedLibrary(bundle->getBuildSharedLibrary()), mNumLocal(0), mBundle(bundle) { @@ -1695,8 +1695,9 @@ status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets const size_t N = incl.getBasePackageCount(); for (size_t phase=0; phase<2; phase++) { for (size_t i=0; i<N; i++) { - String16 name(incl.getBasePackageName(i)); + const String16 name = incl.getBasePackageName(i); uint32_t id = incl.getBasePackageId(i); + // First time through: only add base packages (id // is not 0); second time through add the other // packages. @@ -1722,7 +1723,7 @@ status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets } } if (id != 0) { - NOISY(printf("Including package %s with ID=%d\n", + NOISY(fprintf(stderr, "Including package %s with ID=%d\n", String8(name).string(), id)); sp<Package> p = new Package(name, id); mPackages.add(name, p); @@ -2687,6 +2688,9 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) bool useUTF8 = !bundle->getUTF16StringsOption(); + // The libraries this table references. + Vector<sp<Package> > libraryPackages; + // Iterate through all data, collecting all values (strings, // references, etc). StringPool valueStrings(useUTF8); @@ -2694,8 +2698,22 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) for (pi=0; pi<N; pi++) { sp<Package> p = mOrderedPackages.itemAt(pi); if (p->getTypes().size() == 0) { - // Empty, skip! + // Empty, this is an imported package being used as + // a shared library. We do not flatten this package. + if (p->getAssignedId() != 0x01 && p->getName() != String16("android")) { + // This is not the base Android package, and it is a library + // so we must add a reference to the library when flattening. + libraryPackages.add(p); + } continue; + } else if (p->getAssignedId() == 0x00) { + if (!bundle->getBuildSharedLibrary()) { + fprintf(stderr, "ERROR: Package %s can not have ID=0x00 unless building a shared library.", + String8(p->getName()).string()); + return UNKNOWN_ERROR; + } + // If this is a shared library, we also include ourselves as an entry. + libraryPackages.add(p); } StringPool typeStrings(useUTF8); @@ -2778,7 +2796,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) } ssize_t strAmt = 0; - + // Now build the array of package chunks. Vector<sp<AaptFile> > flatPackages; for (pi=0; pi<N; pi++) { @@ -2827,6 +2845,12 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) return amt; } + err = flattenLibraryTable(data, libraryPackages); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: failed to write library table\n"); + return err; + } + // Build the type chunks inside of this package. for (size_t ti=0; ti<N; ti++) { // Retrieve them in the same order as the type string block. @@ -3053,7 +3077,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) fprintf(stderr, "**** value strings: %d\n", amt); fprintf(stderr, "**** total strings: %d\n", strAmt); #endif - + for (pi=0; pi<flatPackages.size(); pi++) { err = dest->writeData(flatPackages[pi]->getData(), flatPackages[pi]->getSize()); @@ -3078,6 +3102,38 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) return NO_ERROR; } +status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) { + // Write out the library table if necessary + if (libs.size() > 0) { + NOISY(fprintf(stderr, "Writing library reference table\n")); + + const size_t libStart = dest->getSize(); + const size_t count = libs.size(); + ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange(libStart, sizeof(ResTable_lib_header)); + + memset(libHeader, 0, sizeof(*libHeader)); + libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE); + libHeader->header.headerSize = htods(sizeof(*libHeader)); + libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count)); + libHeader->count = htodl(count); + + // Write the library entries + for (size_t i = 0; i < count; i++) { + const size_t entryStart = dest->getSize(); + sp<Package> libPackage = libs[i]; + NOISY(fprintf(stderr, " Entry %s -> 0x%02x\n", + String8(libPackage->getName()).string(), + (uint8_t)libPackage->getAssignedId())); + + ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange(entryStart, sizeof(ResTable_lib_entry)); + memset(entry, 0, sizeof(*entry)); + entry->packageId = htodl(libPackage->getAssignedId()); + strcpy16_htod(entry->packageName, libPackage->getName().string()); + } + } + return NO_ERROR; +} + void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp) { fprintf(fp, @@ -3847,7 +3903,7 @@ sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) return NULL; } mHaveAppPackage = true; - p = new Package(package, 127); + p = new Package(package, mIsSharedLibrary ? 0 : 127); } else { p = new Package(package, mNextPackageId); } diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index 75005cd..ec8fd17 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -224,6 +224,7 @@ public: status_t validateLocalizations(void); status_t flatten(Bundle*, const sp<AaptFile>& dest); + status_t flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs); void writePublicDefinitions(const String16& package, FILE* fp); @@ -546,6 +547,7 @@ private: uint32_t mNextPackageId; bool mHaveAppPackage; bool mIsAppPackage; + bool mIsSharedLibrary; size_t mNumLocal; SourcePos mCurrentXmlPos; Bundle* mBundle; diff --git a/tools/aapt/printapk.cpp b/tools/aapt/printapk.cpp index 4cf73d8..def6e2e 100644 --- a/tools/aapt/printapk.cpp +++ b/tools/aapt/printapk.cpp @@ -115,8 +115,8 @@ main(int argc, char** argv) size_t basePackageCount = res.getBasePackageCount(); printf("Base Packages: %d\n", (int)basePackageCount); for (size_t bpIndex=0; bpIndex<basePackageCount; bpIndex++) { - const char16_t* ch = res.getBasePackageName(bpIndex); - String8 s = String8(String16(ch)); + const String16 ch = res.getBasePackageName(bpIndex); + String8 s = String8(ch); printf(" [%3d] %s\n", (int)bpIndex, s.string()); } #endif |