| Index: web_apks/minting_example/src/org/chromium/minting/MintingApplication.java
|
| diff --git a/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java b/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java
|
| index 0ce9de0ae2e63fd1c23fee9fe67d0655e037dcde..2e23603c310635da54815260a9cebabda1ce0eea 100644
|
| --- a/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java
|
| +++ b/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java
|
| @@ -9,9 +9,8 @@ import android.content.Context;
|
| import android.content.pm.PackageManager.NameNotFoundException;
|
| import android.util.Log;
|
|
|
| -import java.io.File;
|
| import java.lang.reflect.Array;
|
| -import java.lang.reflect.Field;
|
| +import java.util.List;
|
|
|
| /**
|
| * Example application for a minted APK.
|
| @@ -25,49 +24,94 @@ public class MintingApplication extends Application {
|
| private Context mRemoteContext = null;
|
|
|
| private static final String TAG = "cr.MintingApplication";
|
| - private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator
|
| - + "secondary-dexes";
|
| -
|
| - private static Field findField(Object instance, String name) throws NoSuchFieldException {
|
| - for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
|
| - try {
|
| - Field field = clazz.getDeclaredField(name);
|
| - if (!field.isAccessible()) {
|
| - field.setAccessible(true);
|
| - }
|
| - return field;
|
| - } catch (NoSuchFieldException e) {
|
| - // ignore and search next
|
| - }
|
| - }
|
| - throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
|
| - }
|
| -
|
| /**
|
| - * Copy all the objects from a specified field of the second instance to the same field of the
|
| - * first instance. As a result, the given field of the first instance will contain all the
|
| + * Copy all the objects from a specified field of the hostInstance to the same field of the
|
| + * mintInstance. As a result, the given field of the mintInstance will contain all the
|
| * objects from both instances.
|
| - * @param instance the first instance which contains a field of the given fieldName.
|
| + * @param mintInstance the first instance which contains a field of the given fieldName.
|
| * @param fieldName the name of field to expand to an Object array.
|
| - * @param instance2 the second instance which has a field of the given fieldName.
|
| + * @param hostInstance the second instance which has a field of the given fieldName.
|
| * @throws NoSuchFieldException
|
| * @throws IllegalArgumentException
|
| * @throws IllegalAccessException
|
| */
|
| - private static void expandFieldArray(Object instance, String fieldName,
|
| - Object instance2) throws NoSuchFieldException, IllegalArgumentException,
|
| + private static void expandField(Object mintInstance, String fieldName, Object hostInstance)
|
| + throws NoSuchFieldException, IllegalArgumentException,
|
| IllegalAccessException {
|
| - Field jlrField = findField(instance, fieldName);
|
| - Object[] original = (Object[]) jlrField.get(instance);
|
| - Object[] additional = (Object[]) jlrField.get(instance2);
|
| - Object[] combined = (Object[]) Array.newInstance(
|
| - original.getClass().getComponentType(), original.length + additional.length);
|
| - System.arraycopy(original, 0, combined, 0, original.length);
|
| - System.arraycopy(additional, 0, combined, original.length, additional.length);
|
| - jlrField.set(instance, combined);
|
| + try {
|
| + Object hostCurrentDirs = Reflect.getField(hostInstance, fieldName);
|
| + Object mintCurrentDirs = Reflect.getField(mintInstance, fieldName);
|
| + // Switched from an array to an ArrayList in Lollipop.
|
| + if (mintCurrentDirs instanceof List) {
|
| + List<Object> mintDirsAsList = (List<Object>) mintCurrentDirs;
|
| + concatAndRemoveEndDuplicates(mintDirsAsList, (List<Object>) hostCurrentDirs);
|
| + } else {
|
| + Object[] mintDirsAsArray = (Object[]) mintCurrentDirs;
|
| + Object[] hostDirsAsArray = (Object[]) hostCurrentDirs;
|
| + Reflect.setField(mintInstance, fieldName,
|
| + concatAndRemoveEndDuplicates(mintDirsAsArray, hostDirsAsArray));
|
| + }
|
| + } catch (ReflectiveOperationException e) {
|
| + e.printStackTrace();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Combines two arrays into a new array.
|
| + * If the two arrays end with duplicated elements, keep one copy only. For example,
|
| + * the first array is [A, B, C, D], and the second array is [E, F, C, D], the combined one is
|
| + * [A, B, E, F, C, D]. The unique elements are stored before the duplicates.
|
| + * The arrays must be of the same type.
|
| + * @param mintArrays: the array from a WebAPK
|
| + * @param hostArrays: the array from the host
|
| + * @return: a combined array consists of [WebAPK unique elements, host's unique elements,
|
| + * duplicated one]
|
| + */
|
| + private static Object[] concatAndRemoveEndDuplicates(Object[] mintArrays, Object[] hostArrays) {
|
| + int duplicate1 = mintArrays.length - 1;
|
| + int duplicate2 = hostArrays.length - 1;
|
| + int count = 0;
|
| + while (duplicate1 >= 0 && duplicate2 >= 0
|
| + && mintArrays[duplicate1].toString().equals(hostArrays[duplicate2].toString())) {
|
| + --duplicate1;
|
| + --duplicate2;
|
| + ++count;
|
| + }
|
| + Object[] combined = (Object[]) Array.newInstance(mintArrays.getClass().getComponentType(),
|
| + mintArrays.length + hostArrays.length - count);
|
| + if (mintArrays.length - count > 0) {
|
| + System.arraycopy(mintArrays, 0, combined, 0, mintArrays.length - count);
|
| + }
|
| + System.arraycopy(hostArrays, 0, combined, mintArrays.length - count, hostArrays.length);
|
| + return combined;
|
| }
|
|
|
| - private void addExternalLoader() {
|
| + /**
|
| + * Add the unique elements of the second list to the first one.
|
| + * If the two lists end with duplicated elements, keep one copy only. For example,
|
| + * the first list is [A, B, C, D], and the second list is [E, F, C, D], the combined one is
|
| + * [A, B, E, F, C, D]. The unique elements are store before the duplicates.
|
| + * The lists must be of the same type.
|
| + * @param mintList: the list from a WebAPK
|
| + * @param hostList: the list from the host
|
| + * @return: a combined list consists of [WebAPK unique elements, host's unique elements,
|
| + * duplicated one]
|
| + */
|
| + private static void concatAndRemoveEndDuplicates(List<Object> mintList, List<Object> hostList) {
|
| + int duplicate1 = mintList.size() - 1;
|
| + int duplicate2 = hostList.size() - 1;
|
| + while (duplicate1 >= 0 && duplicate2 >= 0
|
| + && mintList.get(duplicate1).toString().equals(hostList.get(duplicate2).toString()))
|
| + {
|
| + --duplicate1;
|
| + --duplicate2;
|
| + }
|
| + for (int i = 0; i < duplicate2 + 1; i++) {
|
| + mintList.add(duplicate1 + i, hostList.get(i));
|
| + }
|
| + }
|
| +
|
| + private void addExternalLoader() throws ReflectiveOperationException {
|
| if (mRemoteContext == null) {
|
| Log.w(TAG, "Failed to add external loader since the remote context is null.");
|
| return;
|
| @@ -75,15 +119,28 @@ public class MintingApplication extends Application {
|
| ClassLoader externalLoader = mRemoteContext.getClassLoader();
|
| ClassLoader mintLoader = getClassLoader();
|
|
|
| - try {
|
| - Field field = findField(externalLoader, "pathList");
|
| - Object dexOriginal = field.get(mintLoader);
|
| - Object dexAdditional = field.get(externalLoader);
|
| - expandFieldArray(dexOriginal, "dexElements", dexAdditional);
|
| - } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e1) {
|
| - e1.printStackTrace();
|
| - throw new RuntimeException(e1);
|
| + Object extDexPathList = Reflect.getField(externalLoader, "pathList");
|
| + Object mintDexPathList = Reflect.getField(mintLoader, "pathList");
|
| + expandField(mintDexPathList, "dexElements", extDexPathList);
|
| + }
|
| +
|
| + private void addNativeLibrarySearchPath() throws ReflectiveOperationException {
|
| + if (mRemoteContext == null) {
|
| + Log.w(TAG, "Failed to add external loader since the remote context is null.");
|
| + return;
|
| }
|
| + ClassLoader externalLoader = mRemoteContext.getClassLoader();
|
| + ClassLoader mintLoader = getClassLoader();
|
| +
|
| + // Since both WebAPK and its host have the "system/lib" and "vendor/lib" at the end of
|
| + // the native library directory path list, we want to add host's directories before the
|
| + // system directory, and only keep one copy of the system directories.
|
| + // This is because when System.loadLibrary() is called, we want it search the host's
|
| + // native library directories first. If a ".so" file doesn't exit, then search /system/lib.
|
| + Object extDexPathList = Reflect.getField(externalLoader, "pathList");
|
| + Object mintDexPathList = Reflect.getField(mintLoader, "pathList");
|
| + expandField(mintDexPathList, "nativeLibraryDirectories", extDexPathList);
|
| + expandField(mintDexPathList, "nativeLibraryPathElements", extDexPathList);
|
| }
|
|
|
| @Override
|
| @@ -96,7 +153,12 @@ public class MintingApplication extends Application {
|
| mRemoteContext = getApplicationContext().createPackageContext(
|
| packageString,
|
| Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
|
| - addExternalLoader();
|
| + try {
|
| + addExternalLoader();
|
| + addNativeLibrarySearchPath();
|
| + } catch (ReflectiveOperationException e) {
|
| + e.printStackTrace();
|
| + }
|
| Log.w(TAG, "Successfully add external loader.");
|
| } catch (NameNotFoundException e) {
|
| e.printStackTrace();
|
|
|