| OLD | NEW | 
|    1 // Copyright 2015 The Chromium Authors. All rights reserved. |    1 // Copyright 2015 The Chromium Authors. All rights reserved. | 
|    2 // Use of this source code is governed by a BSD-style license that can be |    2 // Use of this source code is governed by a BSD-style license that can be | 
|    3 // found in the LICENSE file. |    3 // found in the LICENSE file. | 
|    4  |    4  | 
|    5 package org.chromium.minting; |    5 package org.chromium.minting; | 
|    6  |    6  | 
|    7 import android.app.Application; |    7 import android.app.Application; | 
|    8 import android.content.Context; |    8 import android.content.Context; | 
|    9 import android.content.pm.PackageManager.NameNotFoundException; |    9 import android.content.pm.PackageManager.NameNotFoundException; | 
|   10 import android.util.Log; |   10 import android.util.Log; | 
|   11  |   11  | 
|   12 import java.io.File; |  | 
|   13 import java.lang.reflect.Array; |   12 import java.lang.reflect.Array; | 
|   14 import java.lang.reflect.Field; |   13 import java.util.List; | 
|   15  |   14  | 
|   16 /** |   15 /** | 
|   17  * Example application for a minted APK. |   16  * Example application for a minted APK. | 
|   18  */ |   17  */ | 
|   19 public class MintingApplication extends Application { |   18 public class MintingApplication extends Application { | 
|   20     static final String CHROME_PACKAGE_PREF = "CHROME_PACKAGE_PREF"; |   19     static final String CHROME_PACKAGE_PREF = "CHROME_PACKAGE_PREF"; | 
|   21     static final String MINT_PREFS = "MINT_PREFS"; |   20     static final String MINT_PREFS = "MINT_PREFS"; | 
|   22     static final String DEFAULT_CHROME_PACKAGE_NAME = "com.google.android.apps.c
     hrome"; |   21     static final String DEFAULT_CHROME_PACKAGE_NAME = "com.google.android.apps.c
     hrome"; | 
|   23  |   22  | 
|   24     // Context of Chrome. |   23     // Context of Chrome. | 
|   25     private Context mRemoteContext = null; |   24     private Context mRemoteContext = null; | 
|   26  |   25  | 
|   27     private static final String TAG = "cr.MintingApplication"; |   26     private static final String TAG = "cr.MintingApplication"; | 
|   28     private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.sepa
     rator |  | 
|   29             + "secondary-dexes"; |  | 
|   30  |  | 
|   31     private static Field findField(Object instance, String name) throws NoSuchFi
     eldException { |  | 
|   32         for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.
     getSuperclass()) { |  | 
|   33             try { |  | 
|   34                 Field field = clazz.getDeclaredField(name); |  | 
|   35                 if (!field.isAccessible()) { |  | 
|   36                     field.setAccessible(true); |  | 
|   37                 } |  | 
|   38                 return field; |  | 
|   39             } catch (NoSuchFieldException e) { |  | 
|   40                 // ignore and search next |  | 
|   41             } |  | 
|   42         } |  | 
|   43         throw new NoSuchFieldException("Field " + name + " not found in " + inst
     ance.getClass()); |  | 
|   44     } |  | 
|   45  |  | 
|   46     /** |   27     /** | 
|   47      * Copy all the objects from a specified field of the second instance to the
      same field of the |   28      * Copy all the objects from a specified field of the hostInstance to the sa
     me field of the | 
|   48      * first instance. As a result, the given field of the first instance will c
     ontain all the |   29      * mintInstance. As a result, the given field of the mintInstance will conta
     in all the | 
|   49      * objects from both instances. |   30      * objects from both instances. | 
|   50      * @param instance the first instance which contains a field of the given fi
     eldName. |   31      * @param mintInstance the first instance which contains a field of the give
     n fieldName. | 
|   51      * @param fieldName the name of field to expand to an Object array. |   32      * @param fieldName the name of field to expand to an Object array. | 
|   52      * @param instance2 the second instance which has a field of the given field
     Name. |   33      * @param hostInstance the second instance which has a field of the given fi
     eldName. | 
|   53      * @throws NoSuchFieldException |   34      * @throws NoSuchFieldException | 
|   54      * @throws IllegalArgumentException |   35      * @throws IllegalArgumentException | 
|   55      * @throws IllegalAccessException |   36      * @throws IllegalAccessException | 
|   56      */ |   37      */ | 
|   57     private static void expandFieldArray(Object instance, String fieldName, |   38     private static void expandField(Object mintInstance, String fieldName, Objec
     t hostInstance) | 
|   58             Object instance2) throws NoSuchFieldException, IllegalArgumentExcept
     ion, |   39             throws NoSuchFieldException, IllegalArgumentException, | 
|   59             IllegalAccessException { |   40             IllegalAccessException { | 
|   60         Field jlrField = findField(instance, fieldName); |   41         try { | 
|   61         Object[] original = (Object[]) jlrField.get(instance); |   42             Object hostCurrentDirs = Reflect.getField(hostInstance, fieldName); | 
|   62         Object[] additional = (Object[]) jlrField.get(instance2); |   43             Object mintCurrentDirs = Reflect.getField(mintInstance, fieldName); | 
|   63         Object[] combined = (Object[]) Array.newInstance( |   44             // Switched from an array to an ArrayList in Lollipop. | 
|   64                 original.getClass().getComponentType(), original.length + additi
     onal.length); |   45             if (mintCurrentDirs instanceof List) { | 
|   65         System.arraycopy(original, 0, combined, 0, original.length); |   46                 List<Object> mintDirsAsList = (List<Object>) mintCurrentDirs; | 
|   66         System.arraycopy(additional, 0, combined, original.length, additional.le
     ngth); |   47                 concatAndRemoveEndDuplicates(mintDirsAsList, (List<Object>) host
     CurrentDirs); | 
|   67         jlrField.set(instance, combined); |   48             } else { | 
 |   49                 Object[] mintDirsAsArray = (Object[]) mintCurrentDirs; | 
 |   50                 Object[] hostDirsAsArray = (Object[]) hostCurrentDirs; | 
 |   51                 Reflect.setField(mintInstance, fieldName, | 
 |   52                         concatAndRemoveEndDuplicates(mintDirsAsArray, hostDirsAs
     Array)); | 
 |   53             } | 
 |   54         } catch (ReflectiveOperationException e) { | 
 |   55             e.printStackTrace(); | 
 |   56         } | 
|   68     } |   57     } | 
|   69  |   58  | 
|   70     private void addExternalLoader() { |   59     /** | 
 |   60      * Combines two arrays into a new array. | 
 |   61      * If the two arrays end with duplicated elements, keep one copy only. For e
     xample, | 
 |   62      * the first array is [A, B, C, D], and the second array is [E, F, C, D], th
     e combined one is | 
 |   63      * [A, B, E, F, C, D]. The unique elements are stored before the duplicates. | 
 |   64      *  The arrays must be of the same type. | 
 |   65      * @param mintArrays: the array from a WebAPK | 
 |   66      * @param hostArrays: the array from the host | 
 |   67      * @return: a combined array consists of [WebAPK unique elements, host's uni
     que elements, | 
 |   68      *  duplicated one] | 
 |   69      */ | 
 |   70     private static Object[] concatAndRemoveEndDuplicates(Object[] mintArrays, Ob
     ject[] hostArrays) { | 
 |   71         int duplicate1 = mintArrays.length - 1; | 
 |   72         int duplicate2 = hostArrays.length - 1; | 
 |   73         int count = 0; | 
 |   74         while (duplicate1 >= 0 && duplicate2 >= 0 | 
 |   75                 && mintArrays[duplicate1].toString().equals(hostArrays[duplicate
     2].toString())) { | 
 |   76             --duplicate1; | 
 |   77             --duplicate2; | 
 |   78             ++count; | 
 |   79         } | 
 |   80         Object[] combined = (Object[]) Array.newInstance(mintArrays.getClass().g
     etComponentType(), | 
 |   81                 mintArrays.length + hostArrays.length - count); | 
 |   82         if (mintArrays.length - count > 0) { | 
 |   83             System.arraycopy(mintArrays, 0, combined, 0, mintArrays.length - cou
     nt); | 
 |   84         } | 
 |   85         System.arraycopy(hostArrays, 0, combined, mintArrays.length - count, hos
     tArrays.length); | 
 |   86         return combined; | 
 |   87     } | 
 |   88  | 
 |   89     /** | 
 |   90      * Add the unique elements of the second list to the first one. | 
 |   91      * If the two lists end with duplicated elements, keep one copy only. For ex
     ample, | 
 |   92      * the first list is [A, B, C, D], and the second list is [E, F, C, D], the 
     combined one is | 
 |   93      * [A, B, E, F, C, D]. The unique elements are store before the duplicates. | 
 |   94      * The lists must be of the same type. | 
 |   95      * @param mintList: the list from a WebAPK | 
 |   96      * @param hostList: the list from the host | 
 |   97      * @return: a combined list consists of [WebAPK unique elements, host's uniq
     ue elements, | 
 |   98      *  duplicated one] | 
 |   99      */ | 
 |  100     private static void concatAndRemoveEndDuplicates(List<Object> mintList, List
     <Object> hostList) { | 
 |  101         int duplicate1 = mintList.size() - 1; | 
 |  102         int duplicate2 = hostList.size() - 1; | 
 |  103         while (duplicate1 >= 0 && duplicate2 >= 0 | 
 |  104                 && mintList.get(duplicate1).toString().equals(hostList.get(dupli
     cate2).toString())) | 
 |  105         { | 
 |  106             --duplicate1; | 
 |  107             --duplicate2; | 
 |  108         } | 
 |  109         for (int i = 0; i < duplicate2 + 1; i++) { | 
 |  110             mintList.add(duplicate1 + i, hostList.get(i)); | 
 |  111         } | 
 |  112     } | 
 |  113  | 
 |  114     private void addExternalLoader() throws ReflectiveOperationException { | 
|   71         if (mRemoteContext == null) { |  115         if (mRemoteContext == null) { | 
|   72             Log.w(TAG, "Failed to add external loader since the remote context i
     s null."); |  116             Log.w(TAG, "Failed to add external loader since the remote context i
     s null."); | 
|   73             return; |  117             return; | 
|   74         } |  118         } | 
|   75         ClassLoader externalLoader = mRemoteContext.getClassLoader(); |  119         ClassLoader externalLoader = mRemoteContext.getClassLoader(); | 
|   76         ClassLoader mintLoader = getClassLoader(); |  120         ClassLoader mintLoader = getClassLoader(); | 
|   77  |  121  | 
|   78         try { |  122         Object extDexPathList = Reflect.getField(externalLoader, "pathList"); | 
|   79             Field field = findField(externalLoader, "pathList"); |  123         Object mintDexPathList = Reflect.getField(mintLoader, "pathList"); | 
|   80             Object dexOriginal = field.get(mintLoader); |  124         expandField(mintDexPathList, "dexElements", extDexPathList); | 
|   81             Object dexAdditional = field.get(externalLoader); |  125     } | 
|   82             expandFieldArray(dexOriginal, "dexElements", dexAdditional); |  126  | 
|   83         } catch (NoSuchFieldException | IllegalAccessException | IllegalArgument
     Exception e1) { |  127     private void addNativeLibrarySearchPath() throws ReflectiveOperationExceptio
     n { | 
|   84             e1.printStackTrace(); |  128         if (mRemoteContext == null) { | 
|   85             throw new RuntimeException(e1); |  129             Log.w(TAG, "Failed to add external loader since the remote context i
     s null."); | 
 |  130             return; | 
|   86         } |  131         } | 
 |  132         ClassLoader externalLoader = mRemoteContext.getClassLoader(); | 
 |  133         ClassLoader mintLoader = getClassLoader(); | 
 |  134  | 
 |  135         // Since both WebAPK and its host have the "system/lib" and "vendor/lib"
      at the end of | 
 |  136         // the native library directory path list, we want to add host's directo
     ries before the | 
 |  137         // system directory, and only keep one copy of the system directories. | 
 |  138         // This is because when System.loadLibrary() is called, we want it searc
     h the host's | 
 |  139         // native library directories first. If a ".so" file doesn't exit, then 
     search /system/lib. | 
 |  140         Object extDexPathList = Reflect.getField(externalLoader, "pathList"); | 
 |  141         Object mintDexPathList = Reflect.getField(mintLoader, "pathList"); | 
 |  142         expandField(mintDexPathList, "nativeLibraryDirectories", extDexPathList)
     ; | 
 |  143         expandField(mintDexPathList, "nativeLibraryPathElements", extDexPathList
     ); | 
|   87     } |  144     } | 
|   88  |  145  | 
|   89     @Override |  146     @Override | 
|   90     public void onCreate() { |  147     public void onCreate() { | 
|   91         super.onCreate(); |  148         super.onCreate(); | 
|   92         try { |  149         try { | 
|   93             String packageString = getSharedPreferences(MINT_PREFS, MODE_PRIVATE
     ) |  150             String packageString = getSharedPreferences(MINT_PREFS, MODE_PRIVATE
     ) | 
|   94                     .getString(CHROME_PACKAGE_PREF, DEFAULT_CHROME_PACKAGE_NAME)
     ; |  151                     .getString(CHROME_PACKAGE_PREF, DEFAULT_CHROME_PACKAGE_NAME)
     ; | 
|   95             Log.w(TAG, "Getting remote context for package " + packageString); |  152             Log.w(TAG, "Getting remote context for package " + packageString); | 
|   96             mRemoteContext = getApplicationContext().createPackageContext( |  153             mRemoteContext = getApplicationContext().createPackageContext( | 
|   97                     packageString, |  154                     packageString, | 
|   98                     Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CO
     DE); |  155                     Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CO
     DE); | 
|   99             addExternalLoader(); |  156             try { | 
 |  157                 addExternalLoader(); | 
 |  158                 addNativeLibrarySearchPath(); | 
 |  159             } catch (ReflectiveOperationException e) { | 
 |  160                 e.printStackTrace(); | 
 |  161             } | 
|  100             Log.w(TAG, "Successfully add external loader."); |  162             Log.w(TAG, "Successfully add external loader."); | 
|  101         } catch (NameNotFoundException e) { |  163         } catch (NameNotFoundException e) { | 
|  102             e.printStackTrace(); |  164             e.printStackTrace(); | 
|  103         } |  165         } | 
|  104     } |  166     } | 
|  105 } |  167 } | 
| OLD | NEW |