From de898ff42912bd7ca1bfb099cd439562496765a4 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Wed, 29 Jan 2014 18:20:45 -0800 Subject: [PATCH] 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 --- cmds/idmap/scan.cpp | 3 +- core/java/android/app/ActivityThread.java | 4 +- .../app/ApplicationPackageManager.java | 2 +- core/java/android/app/ContextImpl.java | 5 +- core/java/android/app/LoadedApk.java | 98 ++++- core/java/android/app/ResourcesManager.java | 19 +- .../android/content/res/AssetManager.java | 14 +- core/java/android/content/res/TypedArray.java | 2 +- core/java/android/content/res/XmlBlock.java | 10 +- core/jni/android_util_AssetManager.cpp | 50 ++- core/jni/android_util_XmlBlock.cpp | 7 +- include/androidfw/AssetManager.h | 2 +- include/androidfw/ResourceTypes.h | 86 ++++- libs/androidfw/AssetManager.cpp | 45 ++- libs/androidfw/ResourceTypes.cpp | 359 +++++++++++++++--- libs/androidfw/tests/ObbFile_test.cpp | 2 +- .../SharedLibrary/client/AndroidManifest.xml | 2 +- .../SharedLibrary/client/res/layout/main.xml | 25 ++ .../client/res/values/strings.xml | 1 + .../client/res/values/themes.xml | 6 + .../android/test/lib_client/ActivityMain.java | 23 +- tests/SharedLibrary/lib/Android.mk | 7 +- tests/SharedLibrary/lib/proguard.proguard | 7 + .../SharedLibrary/lib/res/layout/address.xml | 16 + tests/SharedLibrary/lib/res/values/attrs.xml | 13 + tests/SharedLibrary/lib/res/values/public.xml | 17 + .../SharedLibrary/lib/res/values/strings.xml | 9 + tests/SharedLibrary/lib/res/values/themes.xml | 18 + .../test/shared_library/AddressView.java | 44 +++ tools/aapt/AaptAssets.cpp | 5 + tools/aapt/AaptAssets.h | 1 + tools/aapt/Bundle.h | 4 + tools/aapt/Main.cpp | 6 + tools/aapt/ResourceIdCache.cpp | 8 +- tools/aapt/ResourceIdCache.h | 14 +- tools/aapt/ResourceTable.cpp | 70 +++- tools/aapt/ResourceTable.h | 2 + tools/aapt/printapk.cpp | 4 +- 38 files changed, 889 insertions(+), 121 deletions(-) create mode 100644 tests/SharedLibrary/client/res/layout/main.xml create mode 100644 tests/SharedLibrary/client/res/values/themes.xml create mode 100644 tests/SharedLibrary/lib/proguard.proguard create mode 100644 tests/SharedLibrary/lib/res/layout/address.xml create mode 100644 tests/SharedLibrary/lib/res/values/attrs.xml create mode 100644 tests/SharedLibrary/lib/res/values/public.xml create mode 100644 tests/SharedLibrary/lib/res/values/themes.xml create mode 100644 tests/SharedLibrary/lib/src/com/google/android/test/shared_library/AddressView.java diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index c5fc941fa846c..1153f38a9c022 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 69ada6a9d12ec..965f815324363 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 8165fa161d11f..0615bd930b4bc 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 87f47a16b4225..589c82f930509 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 d409352149221..3ae8bfcb4f3fe 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 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 728f3722f8f79..a67faa09be862 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 2f8dd53eb55cd..0c0440135fa42 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); } /** @@ -737,6 +736,11 @@ public final class AssetManager { */ public native final String getCookieName(int cookie); + /** + * {@hide} + */ + public native final SparseArray getAssignedPackageIdentifiers(); + /** * {@hide} */ diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index baf887e31031d..d7199ff87b794 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 3ad357f28704e..2f4d69b06c130 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 9dde701adb1b9..4147608b0026b 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(cookie), fileName8.c_str(), Asset::ACCESS_BUFFER) - : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER); + int32_t assetCookie = static_cast(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, "", "()V"); + LOG_FATAL_IF(gSparseArrayOffsets.constructor == NULL, "Unable to find SparseArray.()"); + 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 03de5c0a1cd91..2cccb83977dc2 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 a13dd16fc7366..610528c72453d 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 b334aab9ea091..7cc10be872acc 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -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; @@ -1384,6 +1395,68 @@ struct ResTable_map Res_value value; }; +/** + * 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& entries() const { + return mEntries; + } + +private: + const uint8_t mAssignedPackageId; + uint8_t mLookupTable[256]; + KeyedVector mEntries; +}; + /** * Convenience class for accessing data in a ResTable resource. */ @@ -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* 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 64363d475a93e..91dda75026ab8 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(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(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(this)->loadFileNameCacheLocked(); + } + mResources = new ResTable(); + updateResourceParamsLocked(); + + bool onlyEmptyResources = true; const size_t N = mAssetPaths.size(); for (size_t i=0; iadd(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 652cd4a142f7b..04ca81eb3f677 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(_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(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* 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(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; iname.ident)); diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp index 2c9f65067cbf8..7a4dd13731388 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 c6a43c0fcd935..a39c7540ceffe 100644 --- a/tests/SharedLibrary/client/AndroidManifest.xml +++ b/tests/SharedLibrary/client/AndroidManifest.xml @@ -16,7 +16,7 @@ - + diff --git a/tests/SharedLibrary/client/res/layout/main.xml b/tests/SharedLibrary/client/res/layout/main.xml new file mode 100644 index 0000000000000..067ef9feb254e --- /dev/null +++ b/tests/SharedLibrary/client/res/layout/main.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml index 3757a2554f8b2..d9efdc7bfffc4 100644 --- a/tests/SharedLibrary/client/res/values/strings.xml +++ b/tests/SharedLibrary/client/res/values/strings.xml @@ -16,4 +16,5 @@ SharedLibrary client + @com.google.android.test.shared_library:string/shared_string diff --git a/tests/SharedLibrary/client/res/values/themes.xml b/tests/SharedLibrary/client/res/values/themes.xml new file mode 100644 index 0000000000000..a14f98a93a091 --- /dev/null +++ b/tests/SharedLibrary/client/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + + 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 d6121a503780b..7276b3c34c158 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); - TextView content = new TextView(this); - content.setText("Library version: " + SharedLibraryMain.getVersion(this) + "!"); + 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."); + } + + 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 c19e23adcbeef..b2fc369527bf3 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 0000000000000..e5dfbe1c453d8 --- /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 0000000000000..835f43e6c9bdb --- /dev/null +++ b/tests/SharedLibrary/lib/res/layout/address.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/tests/SharedLibrary/lib/res/values/attrs.xml b/tests/SharedLibrary/lib/res/values/attrs.xml new file mode 100644 index 0000000000000..8cefe921be8b0 --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/attrs.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tests/SharedLibrary/lib/res/values/public.xml b/tests/SharedLibrary/lib/res/values/public.xml new file mode 100644 index 0000000000000..37b1ec9da8ca4 --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/public.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml index bbfb0b4d81d5f..6827f936ceea4 100644 --- a/tests/SharedLibrary/lib/res/values/strings.xml +++ b/tests/SharedLibrary/lib/res/values/strings.xml @@ -19,4 +19,13 @@ %1$s requires a newer version of %2$s to run. Upgrade + Shared string! + + + @string/racoon + Rhino + Elephant + + + Racoon diff --git a/tests/SharedLibrary/lib/res/values/themes.xml b/tests/SharedLibrary/lib/res/values/themes.xml new file mode 100644 index 0000000000000..f1081ac5092d5 --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/themes.xml @@ -0,0 +1,18 @@ + + + + + + + + + + #f0ad4e + 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 0000000000000..dcaf68cce5795 --- /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 f9a2d19fecc38..38bfa00d7564e 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 336d08ba77b8c..82dda5ff04d37 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 49b8b55cb8dff..d2eccbe8aa015 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 33711fa5ba9b2..1cf4783a8de99 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 e03f4f668a1e7..d60a07fc243bd 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 e6bcda273844f..3acdee1bedfc7 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 + 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 652998ecf32ae..0fb26068dcf86 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 p = new Package(name, id); mPackages.add(name, p); @@ -2687,6 +2688,9 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp& dest) bool useUTF8 = !bundle->getUTF16StringsOption(); + // The libraries this table references. + Vector > 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& dest) for (pi=0; pi 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& dest) } ssize_t strAmt = 0; - + // Now build the array of package chunks. Vector > flatPackages; for (pi=0; pi& 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& dest) fprintf(stderr, "**** value strings: %d\n", amt); fprintf(stderr, "**** total strings: %d\n", strAmt); #endif - + for (pi=0; piwriteData(flatPackages[pi]->getData(), flatPackages[pi]->getSize()); @@ -3078,6 +3102,38 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp& dest) return NO_ERROR; } +status_t ResourceTable::flattenLibraryTable(const sp& dest, const Vector >& 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 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::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 75005cd18f12a..ec8fd175e1610 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& dest); + status_t flattenLibraryTable(const sp& dest, const Vector >& 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 4cf73d81294bd..def6e2e683fc5 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