1667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/* 2667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Copyright (C) 2013 The Android Open Source Project 3667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 4667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Licensed under the Apache License, Version 2.0 (the "License"); 5667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * you may not use this file except in compliance with the License. 6667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * You may obtain a copy of the License at 7667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 8667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * http://www.apache.org/licenses/LICENSE-2.0 9667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 10667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Unless required by applicable law or agreed to in writing, software 11667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * distributed under the License is distributed on an "AS IS" BASIS, 12667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * See the License for the specific language governing permissions and 14667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * limitations under the License. 15667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 16667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 17667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chupackage android.support.multidex; 18667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 19dd3cc22f2fbc8ea4dd5fa88978dc8d1ae5034bd9Yohann Rousselimport android.app.Application; 20667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.content.Context; 21667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.content.pm.ApplicationInfo; 22667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.content.pm.PackageManager; 23d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Rousselimport android.content.pm.PackageManager.NameNotFoundException; 24667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.os.Build; 25667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.util.Log; 26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 27602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Rousselimport dalvik.system.DexFile; 28602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File; 30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException; 31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.Array; 32667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.Field; 33667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.InvocationTargetException; 34667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.lang.reflect.Method; 35667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList; 36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.Arrays; 37667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.HashSet; 38667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List; 39667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ListIterator; 40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.Set; 417b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Rousselimport java.util.regex.Matcher; 427b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Rousselimport java.util.regex.Pattern; 4366f379f774e06e650375750d18b1695ddb853371Maurice Chuimport java.util.zip.ZipFile; 44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/** 46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Monkey patches {@link Context#getClassLoader() the application context class 47667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * loader} in order to load classes from more than one dex file. The primary 48dd3cc22f2fbc8ea4dd5fa88978dc8d1ae5034bd9Yohann Roussel * {@code classes.dex} must contain the classes necessary for calling this 49dd3cc22f2fbc8ea4dd5fa88978dc8d1ae5034bd9Yohann Roussel * class methods. Secondary dex files named classes2.dex, classes3.dex... found 50dd3cc22f2fbc8ea4dd5fa88978dc8d1ae5034bd9Yohann Roussel * in the application apk will be added to the classloader after first call to 51667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@link #install(Context)}. 52667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 53667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * <p/> 547b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel * This library provides compatibility for platforms with API level 4 through 20. This library does 557b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel * nothing on newer versions of the platform which provide built-in support for secondary dex files. 56667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 57667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chupublic final class MultiDex { 58667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 59667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu static final String TAG = "MultiDex"; 60667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 61590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes"; 62590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel 63606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel private static final String CODE_CACHE_NAME = "code_cache"; 64606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel 65606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel private static final String CODE_CACHE_SECONDARY_FOLDER_NAME = "secondary-dexes"; 66667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 677b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel private static final int MAX_SUPPORTED_SDK_VERSION = 20; 68667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 69667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final int MIN_SDK_VERSION = 4; 70667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 717b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2; 727b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel 737b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1; 747b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel 75667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final Set<String> installedApk = new HashSet<String>(); 76667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 777b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel private static final boolean IS_VM_MULTIDEX_CAPABLE = 787b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel isVMMultidexCapable(System.getProperty("java.vm.version")); 797b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel 80667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private MultiDex() {} 81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 82667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 83667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Patches the application context class loader by appending extra dex files 84dd3cc22f2fbc8ea4dd5fa88978dc8d1ae5034bd9Yohann Roussel * loaded from the application apk. This method should be called in the 85dd3cc22f2fbc8ea4dd5fa88978dc8d1ae5034bd9Yohann Roussel * attachBaseContext of your {@link Application}, see 86dd3cc22f2fbc8ea4dd5fa88978dc8d1ae5034bd9Yohann Roussel * {@link MultiDexApplication} for more explanation and an example. 87667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 88667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param context application context. 89667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws RuntimeException if an error occurred preventing the classloader 90667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * extension. 91667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 92667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu public static void install(Context context) { 93602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel Log.i(TAG, "install"); 947b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel if (IS_VM_MULTIDEX_CAPABLE) { 957b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel Log.i(TAG, "VM has multidex support, MultiDex support library is disabled."); 967b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel return; 977b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel } 98667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 99667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) { 100667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT 101667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); 102667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 103667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 104667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 105d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel ApplicationInfo applicationInfo = getApplicationInfo(context); 106667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (applicationInfo == null) { 107d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel // Looks like running on a test Context, so just return without patching. 108667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 109667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 110667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 111667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu synchronized (installedApk) { 112667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String apkPath = applicationInfo.sourceDir; 113667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (installedApk.contains(apkPath)) { 114667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 115667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 116667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu installedApk.add(apkPath); 117667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 1187b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) { 1197b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel Log.w(TAG, "MultiDex is not guaranteed to work in SDK version " 1207b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel + Build.VERSION.SDK_INT + ": SDK version higher than " 1217b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel + MAX_SUPPORTED_SDK_VERSION + " should be backed by " 1227b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel + "runtime with built-in multidex capabilty but it's not the " 1237b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel + "case here: java.vm.version=\"" 1247b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel + System.getProperty("java.vm.version") + "\""); 125667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 126667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 127667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 128667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.BaseDexClassLoader. We modify its 129667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexPathList pathList field to append additional DEX 130667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 131667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 132667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ClassLoader loader; 133667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 134667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu loader = context.getClassLoader(); 135667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (RuntimeException e) { 136667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* Ignore those exceptions so that we don't break tests relying on Context like 137667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * a android.test.mock.MockContext or a android.content.ContextWrapper with a 138667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * null base Context. 139667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 140667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failure while trying to obtain Context class loader. " + 141667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu "Must be running in test mode. Skip patching.", e); 142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (loader == null) { 145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // Note, the context class loader is null when running Robolectric tests. 146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.e(TAG, 147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu "Context class loader is null. Must be running in test mode. " 148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu + "Skip patching."); 149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 151667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 152590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel try { 153590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel clearOldDexDir(context); 154590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel } catch (Throwable t) { 155590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, " 156590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel + "continuing without cleaning.", t); 157590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel } 158590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel 159606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel File dexDir = getDexDir(context, applicationInfo); 1607e267a38525afac2a571da186e770a2b86a01846Maurice Chu List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); 161cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu if (checkValidZipFiles(files)) { 162cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu installSecondaryDexes(loader, dexDir, files); 163cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } else { 164cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu Log.w(TAG, "Files were not valid zip files. Forcing a reload."); 165cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu // Try again, but this time force a reload of the zip file. 1667e267a38525afac2a571da186e770a2b86a01846Maurice Chu files = MultiDexExtractor.load(context, applicationInfo, dexDir, true); 167602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 168cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu if (checkValidZipFiles(files)) { 169cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu installSecondaryDexes(loader, dexDir, files); 170667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } else { 171cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu // Second time didn't work, give up 172cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu throw new RuntimeException("Zip files were not valid."); 173667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 174667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 175667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 176667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 177667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (Exception e) { 178667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.e(TAG, "Multidex installation failure", e); 179667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ")."); 180667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 181602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel Log.i(TAG, "install done"); 182667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 183667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 184d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel private static ApplicationInfo getApplicationInfo(Context context) 185d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel throws NameNotFoundException { 186d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel PackageManager pm; 187d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel String packageName; 188d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel try { 189d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel pm = context.getPackageManager(); 190d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel packageName = context.getPackageName(); 191d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } catch (RuntimeException e) { 192d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel /* Ignore those exceptions so that we don't break tests relying on Context like 193d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel * a android.test.mock.MockContext or a android.content.ContextWrapper with a null 194d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel * base Context. 195d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel */ 196d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " + 197d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel "Must be running in test mode. Skip patching.", e); 198d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel return null; 199d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 200d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel if (pm == null || packageName == null) { 201d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel // This is most likely a mock context, so just return without patching. 202d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel return null; 203d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 204d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel ApplicationInfo applicationInfo = 205d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); 206d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel return applicationInfo; 207d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 208d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel 2097b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel /** 2107b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel * Identifies if the current VM has a native support for multidex, meaning there is no need for 2117b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel * additional installation by this library. 2127b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel * @return true if the VM handles multidex 2137b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel */ 2147b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel /* package visible for test */ 2157b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel static boolean isVMMultidexCapable(String versionString) { 2167b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel boolean isMultidexCapable = false; 2177b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel if (versionString != null) { 2187b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); 2197b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel if (matcher.matches()) { 2207b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel try { 2217b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel int major = Integer.parseInt(matcher.group(1)); 2227b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel int minor = Integer.parseInt(matcher.group(2)); 2237b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR) 2247b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR) 2257b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR)); 2267b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel } catch (NumberFormatException e) { 2277b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel // let isMultidexCapable be false 2287b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel } 2297b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel } 2307b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel } 2317b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel Log.i(TAG, "VM with version " + versionString + 2327b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel (isMultidexCapable ? 2337b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel " has multidex support" : 2347b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel " does not have multidex support")); 2357b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel return isMultidexCapable; 2367b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel } 2377b86f7e21c70ac06129c05ed137e585a308c6fd1Yohann Roussel 238cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files) 239cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, 240cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu InvocationTargetException, NoSuchMethodException, IOException { 241cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu if (!files.isEmpty()) { 242cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu if (Build.VERSION.SDK_INT >= 19) { 243cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu V19.install(loader, files, dexDir); 244cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } else if (Build.VERSION.SDK_INT >= 14) { 245cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu V14.install(loader, files, dexDir); 246cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } else { 247cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu V4.install(loader, files); 248cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } 249cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } 250cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } 251cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu 252cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu /** 253cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu * Returns whether all files in the list are valid zip files. If {@code files} is empty, then 254cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu * returns true. 255cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu */ 256cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu private static boolean checkValidZipFiles(List<File> files) { 257cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu for (File file : files) { 258cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu if (!MultiDexExtractor.verifyZipFile(file)) { 259cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu return false; 260cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } 261cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } 262cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu return true; 263cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu } 264cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu 265667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 266667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Locates a given field anywhere in the class inheritance hierarchy. 267667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 268667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param instance an object to search the field into. 269667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param name field name 270667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @return a field object 271667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws NoSuchFieldException if the field cannot be located 272667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 273667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Field findField(Object instance, String name) throws NoSuchFieldException { 274667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { 275667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 276667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field field = clazz.getDeclaredField(name); 277667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 278667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 279667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!field.isAccessible()) { 280667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu field.setAccessible(true); 281667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 282667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 283667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return field; 284667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (NoSuchFieldException e) { 285667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // ignore and search next 286667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 287667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 288667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 289667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); 290667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 291667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 292667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 293667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Locates a given method anywhere in the class inheritance hierarchy. 294667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 295667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param instance an object to search the method into. 296667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param name method name 297667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param parameterTypes method parameter types 298667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @return a method object 299667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws NoSuchMethodException if the method cannot be located 300667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 301667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Method findMethod(Object instance, String name, Class<?>... parameterTypes) 302667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws NoSuchMethodException { 303667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { 304667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 305667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Method method = clazz.getDeclaredMethod(name, parameterTypes); 306667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 307667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 308667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!method.isAccessible()) { 309667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu method.setAccessible(true); 310667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 311667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 312667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return method; 313667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (NoSuchMethodException e) { 314667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // ignore and search next 315667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 316667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 317667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 318667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new NoSuchMethodException("Method " + name + " with parameters " + 319667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Arrays.asList(parameterTypes) + " not found in " + instance.getClass()); 320667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 321667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 322667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 323667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Replace the value of a field containing a non null array, by a new array containing the 324667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * elements of the original array plus the elements of extraElements. 325667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param instance the instance whose field is to be modified. 326667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param fieldName the field to modify. 327667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param extraElements elements to append at the end of the array. 328667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 329667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void expandFieldArray(Object instance, String fieldName, 330667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, 331667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu IllegalAccessException { 332667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field jlrField = findField(instance, fieldName); 333667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object[] original = (Object[]) jlrField.get(instance); 334667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object[] combined = (Object[]) Array.newInstance( 335667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu original.getClass().getComponentType(), original.length + extraElements.length); 336667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu System.arraycopy(original, 0, combined, 0, original.length); 337667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); 338667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu jlrField.set(instance, combined); 339667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 340667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 341d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel private static void clearOldDexDir(Context context) throws Exception { 342590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel File dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME); 343d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel if (dexDir.isDirectory()) { 344d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ")."); 345d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel File[] files = dexDir.listFiles(); 346d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel if (files == null) { 347d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); 348d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel return; 349d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 350d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel for (File oldFile : files) { 351d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " 352d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel + oldFile.length()); 353d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel if (!oldFile.delete()) { 354d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); 355d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } else { 356d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.i(TAG, "Deleted old file " + oldFile.getPath()); 357d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 358d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 359d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel if (!dexDir.delete()) { 360d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath()); 361d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } else { 362d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath()); 363d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 364d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 365d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel } 366d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel 367606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel private static File getDexDir(Context context, ApplicationInfo applicationInfo) 368606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel throws IOException { 369606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel File cache = new File(applicationInfo.dataDir, CODE_CACHE_NAME); 370606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel try { 371606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel mkdirChecked(cache); 372606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel } catch (IOException e) { 373606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel /* If we can't emulate code_cache, then store to filesDir. This means abandoning useless 374606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel * files on disk if the device ever updates to android 5+. But since this seems to 375606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel * happen only on some devices running android 2, this should cause no pollution. 376606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel */ 377606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel cache = new File(context.getFilesDir(), CODE_CACHE_NAME); 378606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel mkdirChecked(cache); 379606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel } 380606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel File dexDir = new File(cache, CODE_CACHE_SECONDARY_FOLDER_NAME); 381606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel mkdirChecked(dexDir); 382606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel return dexDir; 383606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel } 384606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel 385606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel private static void mkdirChecked(File dir) throws IOException { 386606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel dir.mkdir(); 387606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel if (!dir.isDirectory()) { 388606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel File parent = dir.getParentFile(); 389606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel if (parent == null) { 390606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel Log.e(TAG, "Failed to create dir " + dir.getPath() + ". Parent file is null."); 391606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel } else { 392606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel Log.e(TAG, "Failed to create dir " + dir.getPath() + 393606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel ". parent file is a dir " + parent.isDirectory() + 394606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel ", a file " + parent.isFile() + 395606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel ", exists " + parent.exists() + 396606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel ", readable " + parent.canRead() + 397606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel ", writable " + parent.canWrite()); 398606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel } 399606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel throw new IOException("Failed to create directory " + dir.getPath()); 400606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel } 401606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel } 402606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel 403667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 404667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Installer for platform versions 19. 405667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 406667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final class V19 { 407667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 408667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void install(ClassLoader loader, List<File> additionalClassPathEntries, 409667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File optimizedDirectory) 410667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalArgumentException, IllegalAccessException, 411667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchFieldException, InvocationTargetException, NoSuchMethodException { 412667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 413667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.BaseDexClassLoader. We modify its 414667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexPathList pathList field to append additional DEX 415667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 416667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 417667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field pathListField = findField(loader, "pathList"); 418667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList = pathListField.get(loader); 419667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 420667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, 421667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new ArrayList<File>(additionalClassPathEntries), optimizedDirectory, 422667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions)); 423667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (suppressedExceptions.size() > 0) { 42488117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel for (IOException e : suppressedExceptions) { 42588117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel Log.w(TAG, "Exception in makeDexElement", e); 42688117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel } 427667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field suppressedExceptionsField = 4281247e62e916098962f95dcba3b49d84e5ace380bYohann Roussel findField(dexPathList, "dexElementsSuppressedExceptions"); 429667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu IOException[] dexElementsSuppressedExceptions = 4301247e62e916098962f95dcba3b49d84e5ace380bYohann Roussel (IOException[]) suppressedExceptionsField.get(dexPathList); 431667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 432667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (dexElementsSuppressedExceptions == null) { 433667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexElementsSuppressedExceptions = 434667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions.toArray( 435667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new IOException[suppressedExceptions.size()]); 436667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } else { 437667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu IOException[] combined = 438667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new IOException[suppressedExceptions.size() + 439667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexElementsSuppressedExceptions.length]; 440667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions.toArray(combined); 441667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu System.arraycopy(dexElementsSuppressedExceptions, 0, combined, 442667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions.size(), dexElementsSuppressedExceptions.length); 443667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexElementsSuppressedExceptions = combined; 444667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 445667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 4461247e62e916098962f95dcba3b49d84e5ace380bYohann Roussel suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions); 447667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 448667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 449667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 450667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 451667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * A wrapper around 452667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code private static final dalvik.system.DexPathList#makeDexElements}. 453667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 454667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Object[] makeDexElements( 455667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList, ArrayList<File> files, File optimizedDirectory, 456667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ArrayList<IOException> suppressedExceptions) 457667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalAccessException, InvocationTargetException, 458667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchMethodException { 459667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Method makeDexElements = 460667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, 461667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ArrayList.class); 462667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 463667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, 464667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu suppressedExceptions); 465667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 466667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 467667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 468667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 469667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Installer for platform versions 14, 15, 16, 17 and 18. 470667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 471667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final class V14 { 472667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 473667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void install(ClassLoader loader, List<File> additionalClassPathEntries, 474667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File optimizedDirectory) 475667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalArgumentException, IllegalAccessException, 476667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchFieldException, InvocationTargetException, NoSuchMethodException { 477667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 478667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.BaseDexClassLoader. We modify its 479667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexPathList pathList field to append additional DEX 480667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 481667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 482667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field pathListField = findField(loader, "pathList"); 483667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList = pathListField.get(loader); 484667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, 485667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu new ArrayList<File>(additionalClassPathEntries), optimizedDirectory)); 486667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 487667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 488667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 489667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * A wrapper around 490667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code private static final dalvik.system.DexPathList#makeDexElements}. 491667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 492667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static Object[] makeDexElements( 493667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Object dexPathList, ArrayList<File> files, File optimizedDirectory) 494667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalAccessException, InvocationTargetException, 495667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchMethodException { 496667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Method makeDexElements = 497667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); 498667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 499667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); 500667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 501667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 502667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 503667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 504667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Installer for platform versions 4 to 13. 505667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 506667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final class V4 { 50752eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel private static void install(ClassLoader loader, List<File> additionalClassPathEntries) 508667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IllegalArgumentException, IllegalAccessException, 509667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu NoSuchFieldException, IOException { 510667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /* The patched class loader is expected to be a descendant of 511667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * dalvik.system.DexClassLoader. We modify its 51252eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel * fields mPaths, mFiles, mZips and mDexs to append additional DEX 513667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * file entries. 514667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 515667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int extraSize = additionalClassPathEntries.size(); 516667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 517667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Field pathField = findField(loader, "path"); 518667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 519667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu StringBuilder path = new StringBuilder((String) pathField.get(loader)); 520667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String[] extraPaths = new String[extraSize]; 521667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File[] extraFiles = new File[extraSize]; 52266f379f774e06e650375750d18b1695ddb853371Maurice Chu ZipFile[] extraZips = new ZipFile[extraSize]; 523667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu DexFile[] extraDexs = new DexFile[extraSize]; 524667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (ListIterator<File> iterator = additionalClassPathEntries.listIterator(); 525667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu iterator.hasNext();) { 526667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File additionalEntry = iterator.next(); 527667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String entryPath = additionalEntry.getAbsolutePath(); 528667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu path.append(':').append(entryPath); 529667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int index = iterator.previousIndex(); 530667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extraPaths[index] = entryPath; 531667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extraFiles[index] = additionalEntry; 53266f379f774e06e650375750d18b1695ddb853371Maurice Chu extraZips[index] = new ZipFile(additionalEntry); 533667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0); 534667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 535667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 536667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu pathField.set(loader, path.toString()); 537667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(loader, "mPaths", extraPaths); 538667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(loader, "mFiles", extraFiles); 53966f379f774e06e650375750d18b1695ddb853371Maurice Chu expandFieldArray(loader, "mZips", extraZips); 540667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu expandFieldArray(loader, "mDexs", extraDexs); 541667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 542667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 543667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 544667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu} 545