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
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 @@
     /**
      * 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 @@
         }
         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 @@
                     || (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.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.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 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 @@
                 }
             }
         }
-        
+
+        // 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 @@
      * 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 @@
             }
         }
 
+        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 @@
 
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.TypedValue;
 
 import java.io.FileNotFoundException;
@@ -266,11 +267,9 @@
         }
     }
 
-    /*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 @@
     /**
      * {@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 @@
             }
             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 3ad357f2..2f4d69b0 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -375,7 +375,7 @@
                 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 @@
         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 @@
         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 @@
         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 @@
         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 9dde701a..4147608 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -73,6 +73,13 @@
     jfieldID mObject;
 } gAssetManagerOffsets;
 
+static struct sparsearray_offsets_t
+{
+    jclass classObject;
+    jmethodID constructor;
+    jmethodID put;
+} gSparseArrayOffsets;
+
 jclass g_stringClass = NULL;
 
 // ----------------------------------------------------------------------------
@@ -905,6 +912,26 @@
     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 @@
         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 @@
         (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 @@
     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 @@
     }
 
     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 @@
                 return 1;
             case ResXMLParser::BAD_DOCUMENT:
                 goto bad;
+            default:
+                break;
         }
     } while (true);