MultiDex.java revision 7e267a38525afac2a571da186e770a2b86a01846
11935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov/* 21935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * Copyright (C) 2013 The Android Open Source Project 31935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * 41935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * Licensed under the Apache License, Version 2.0 (the "License"); 51935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * you may not use this file except in compliance with the License. 61935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * You may obtain a copy of the License at 71935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * 81935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * http://www.apache.org/licenses/LICENSE-2.0 91935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * 101935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * Unless required by applicable law or agreed to in writing, software 111935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * distributed under the License is distributed on an "AS IS" BASIS, 121935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 131935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * See the License for the specific language governing permissions and 141935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * limitations under the License. 151935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 161935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 171935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovpackage android.support.multidex; 181935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 1944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackbornimport dalvik.system.DexFile; 2044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 211935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport android.content.Context; 221935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport android.content.pm.ApplicationInfo; 231935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport android.content.pm.PackageManager; 2444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackbornimport android.os.Build; 251935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport android.util.Log; 261935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 270574ca37da4619afe4e26753f5a1b4de314b6565Svetoslav Ganovimport java.io.File; 280574ca37da4619afe4e26753f5a1b4de314b6565Svetoslav Ganovimport java.io.IOException; 291935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport java.lang.reflect.Array; 301935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport java.lang.reflect.Field; 311935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport java.lang.reflect.InvocationTargetException; 321935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport java.lang.reflect.Method; 331935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport java.util.ArrayList; 3444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackbornimport java.util.Arrays; 3544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackbornimport java.util.HashSet; 3644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackbornimport java.util.List; 371935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport java.util.ListIterator; 381935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovimport java.util.Set; 3944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackbornimport java.util.zip.ZipFile; 4044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 4144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn/** 4244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * Monkey patches {@link Context#getClassLoader() the application context class 4344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * loader} in order to load classes from more than one dex file. The primary 4444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * {@code classes.dex} file necessary for calling this class methods. secondary 4544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * dex files named classes2.dex, classes".dex... found in the application apk 4644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * will be added to the classloader after first call to 4744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * {@link #install(Context)}. 4844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * 4944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * <p/> 5044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * <strong>IMPORTANT:</strong>This library provides compatibility for platforms 511935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * with API level 4 through 19. This library does nothing on newer versions of 521935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * the platform which provide built-in support for secondary dex files. 531935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 541935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganovpublic final class MultiDex { 551935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 561935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov static final String TAG = "MultiDex"; 571935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 581935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov private static final String SECONDARY_FOLDER_NAME = "secondary-dexes"; 591935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 601935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov private static final int SUPPORTED_MULTIDEX_SDK_VERSION = 20; 6144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 6244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static final int MIN_SDK_VERSION = 4; 6344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 6444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static final Set<String> installedApk = new HashSet<String>(); 6544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 6644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private MultiDex() {} 6744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 6844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /** 6944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * Patches the application context class loader by appending extra dex files 7044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * loaded from the application apk. Call this method first thing in your 7144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * {@code Application#OnCreate}, {@code Instrumentation#OnCreate}, 7244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * {@code BackupAgent#OnCreate}, {@code Service#OnCreate}, 731935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * {@code BroadcastReceiver#onReceive}, {@code Activity#OnCreate} and 741935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * {@code ContentProvider#OnCreate} . 751935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * 761935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * @param context application context. 771935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * @throws RuntimeException if an error occurred preventing the classloader 781935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * extension. 7944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 8044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn public static void install(Context context) { 8144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 8244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) { 8344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT 8444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); 8544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 8644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 8744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 8844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn try { 8944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn PackageManager pm; 9044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn String packageName; 9144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn try { 9244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn pm = context.getPackageManager(); 9344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn packageName = context.getPackageName(); 941935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } catch (RuntimeException e) { 9544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /* Ignore those exceptions so that we don't break tests relying on Context like 9644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * a android.test.mock.MockContext or a android.content.ContextWrapper with a null 9744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * base Context. 9844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 9944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " + 10044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn "Must be running in test mode. Skip patching.", e); 10144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return; 10244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 10344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (pm == null || packageName == null) { 10444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn // This is most likely a mock context, so just return without patching. 10544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return; 10644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 10744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn ApplicationInfo applicationInfo = 10844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); 10944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (applicationInfo == null) { 11044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn // This is from a mock context, so just return without patching. 11144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return; 11244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 11344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 11444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn synchronized (installedApk) { 11544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn String apkPath = applicationInfo.sourceDir; 11644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (installedApk.contains(apkPath)) { 11744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return; 11844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 11944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn installedApk.add(apkPath); 12044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 12144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (Build.VERSION.SDK_INT >= SUPPORTED_MULTIDEX_SDK_VERSION) { 12244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn // STOPSHIP: Any app that uses this class needs approval before being released 12344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn // as well as figuring out what the right behavior should be here. 12444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throw new RuntimeException("Platform support of multidex for SDK " + 12544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Build.VERSION.SDK_INT + " has not been confirmed yet."); 12644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 12744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 12844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /* The patched class loader is expected to be a descendant of 12944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * dalvik.system.BaseDexClassLoader. We modify its 13044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * dalvik.system.DexPathList pathList field to append additional DEX 13144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * file entries. 1321935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 1331935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov ClassLoader loader; 1341935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov try { 1351935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov loader = context.getClassLoader(); 1361935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } catch (RuntimeException e) { 1371935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov /* Ignore those exceptions so that we don't break tests relying on Context like 1381935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * a android.test.mock.MockContext or a android.content.ContextWrapper with a 1391935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * null base Context. 1401935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 1411935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Log.w(TAG, "Failure while trying to obtain Context class loader. " + 1421935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov "Must be running in test mode. Skip patching.", e); 14344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return; 14444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 14544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (loader == null) { 14644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn // Note, the context class loader is null when running Robolectric tests. 14744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Log.e(TAG, 1481935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov "Context class loader is null. Must be running in test mode. " 1491935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov + "Skip patching."); 1501935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov return; 1511935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 1521935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 1531935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov File dexDir = new File(context.getFilesDir(), SECONDARY_FOLDER_NAME); 1541935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); 1551935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov if (checkValidZipFiles(files)) { 1561935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov installSecondaryDexes(loader, dexDir, files); 1571935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } else { 1581935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Log.w(TAG, "Files were not valid zip files. Forcing a reload."); 1591935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov // Try again, but this time force a reload of the zip file. 1601935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov files = MultiDexExtractor.load(context, applicationInfo, dexDir, true); 1611935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov if (checkValidZipFiles(files)) { 1621935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov installSecondaryDexes(loader, dexDir, files); 1631935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } else { 1641935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov // Second time didn't work, give up 1651935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov throw new RuntimeException("Zip files were not valid."); 16644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 16744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 16844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 16944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 17044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } catch (Exception e) { 17144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Log.e(TAG, "Multidex installation failure", e); 17244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ")."); 17344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 17444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 17544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 17644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files) 17744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, 17844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn InvocationTargetException, NoSuchMethodException, IOException { 17944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (!files.isEmpty()) { 18044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (Build.VERSION.SDK_INT >= 19) { 18144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn V19.install(loader, files, dexDir); 18244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } else if (Build.VERSION.SDK_INT >= 14) { 18344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn V14.install(loader, files, dexDir); 18444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } else { 18544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn V4.install(loader, files); 18644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 18744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 18844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 18944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 19044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /** 19144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * Returns whether all files in the list are valid zip files. If {@code files} is empty, then 19244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * returns true. 19344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 19444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static boolean checkValidZipFiles(List<File> files) { 19544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn for (File file : files) { 19644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (!MultiDexExtractor.verifyZipFile(file)) { 19744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return false; 19844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 19944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 20044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return true; 20144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 20244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 20344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /** 20444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * Locates a given field anywhere in the class inheritance hierarchy. 20544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * 20644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @param instance an object to search the field into. 20744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @param name field name 20844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @return a field object 20944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @throws NoSuchFieldException if the field cannot be located 21044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 21144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static Field findField(Object instance, String name) throws NoSuchFieldException { 21244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { 21344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn try { 21444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Field field = clazz.getDeclaredField(name); 21544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 21644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 21744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (!field.isAccessible()) { 21844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn field.setAccessible(true); 21944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 22044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 22144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return field; 22244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } catch (NoSuchFieldException e) { 22344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn // ignore and search next 22444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 22544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 22644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 22744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); 22844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 22944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 23044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /** 23144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * Locates a given method anywhere in the class inheritance hierarchy. 23244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * 23344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @param instance an object to search the method into. 23444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @param name method name 23544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @param parameterTypes method parameter types 23644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @return a method object 23744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * @throws NoSuchMethodException if the method cannot be located 23844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 23944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static Method findMethod(Object instance, String name, Class<?>... parameterTypes) 24044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throws NoSuchMethodException { 24144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { 24244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn try { 24344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Method method = clazz.getDeclaredMethod(name, parameterTypes); 24444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 24544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 24644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (!method.isAccessible()) { 24744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn method.setAccessible(true); 24844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 24944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 2501935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov return method; 2511935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } catch (NoSuchMethodException e) { 2521935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov // ignore and search next 2531935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 2541935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 25544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 25644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throw new NoSuchMethodException("Method " + name + " with parameters " + 25744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Arrays.asList(parameterTypes) + " not found in " + instance.getClass()); 2581935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 2591935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 2601935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov /** 2611935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * Replace the value of a field containing a non null array, by a new array containing the 2621935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * elements of the original array plus the elements of extraElements. 2631935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * @param instance the instance whose field is to be modified. 2641935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * @param fieldName the field to modify. 2651935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * @param extraElements elements to append at the end of the array. 2661935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 2671935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov private static void expandFieldArray(Object instance, String fieldName, 2681935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, 269fe32563fd610767a2d3eea8dbd96e6bae87739d5Svetoslav Ganov IllegalAccessException { 2701935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Field jlrField = findField(instance, fieldName); 2711935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Object[] original = (Object[]) jlrField.get(instance); 2721935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Object[] combined = (Object[]) Array.newInstance( 2731935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov original.getClass().getComponentType(), original.length + extraElements.length); 2741935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov System.arraycopy(original, 0, combined, 0, original.length); 2751935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); 2761935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov jlrField.set(instance, combined); 2771935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 2781935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 2791935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov /** 28044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * Installer for platform versions 19. 28144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 28244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static final class V19 { 28344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 28444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static void install(ClassLoader loader, List<File> additionalClassPathEntries, 28571249413434c56006d2cb5d689198a2814c8e6b7Dianne Hackborn File optimizedDirectory) 28644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throws IllegalArgumentException, IllegalAccessException, 28744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn NoSuchFieldException, InvocationTargetException, NoSuchMethodException { 28844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /* The patched class loader is expected to be a descendant of 28944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * dalvik.system.BaseDexClassLoader. We modify its 29044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * dalvik.system.DexPathList pathList field to append additional DEX 29144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * file entries. 29244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 29344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Field pathListField = findField(loader, "pathList"); 29444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Object dexPathList = pathListField.get(loader); 29544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 29644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, 29744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn new ArrayList<File>(additionalClassPathEntries), optimizedDirectory, 29844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn suppressedExceptions)); 29944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (suppressedExceptions.size() > 0) { 30044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn for (IOException e : suppressedExceptions) { 30144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Log.w(TAG, "Exception in makeDexElement", e); 30244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 30344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Field suppressedExceptionsField = 30444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn findField(loader, "dexElementsSuppressedExceptions"); 30544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn IOException[] dexElementsSuppressedExceptions = 30644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn (IOException[]) suppressedExceptionsField.get(loader); 30744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 30844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn if (dexElementsSuppressedExceptions == null) { 30944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn dexElementsSuppressedExceptions = 31044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn suppressedExceptions.toArray( 31144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn new IOException[suppressedExceptions.size()]); 31244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } else { 31344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn IOException[] combined = 31444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn new IOException[suppressedExceptions.size() + 31544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn dexElementsSuppressedExceptions.length]; 31644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn suppressedExceptions.toArray(combined); 31744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn System.arraycopy(dexElementsSuppressedExceptions, 0, combined, 31844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn suppressedExceptions.size(), dexElementsSuppressedExceptions.length); 31944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn dexElementsSuppressedExceptions = combined; 3201935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 3211935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 3221935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov suppressedExceptionsField.set(loader, dexElementsSuppressedExceptions); 3231935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 3241935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 3251935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 3261935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov /** 3271935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * A wrapper around 3281935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * {@code private static final dalvik.system.DexPathList#makeDexElements}. 3291935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 3301935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov private static Object[] makeDexElements( 3311935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Object dexPathList, ArrayList<File> files, File optimizedDirectory, 3321935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov ArrayList<IOException> suppressedExceptions) 3331935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov throws IllegalAccessException, InvocationTargetException, 3341935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov NoSuchMethodException { 3351935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Method makeDexElements = 3361935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, 3371935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov ArrayList.class); 3381935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 3391935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, 3401935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov suppressedExceptions); 3411935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 3421935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 3431935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 3441935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov /** 3451935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * Installer for platform versions 14, 15, 16, 17 and 18. 3461935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 3471935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov private static final class V14 { 3481935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 3491935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov private static void install(ClassLoader loader, List<File> additionalClassPathEntries, 3501935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov File optimizedDirectory) 3511935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov throws IllegalArgumentException, IllegalAccessException, 3521935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov NoSuchFieldException, InvocationTargetException, NoSuchMethodException { 3531935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov /* The patched class loader is expected to be a descendant of 3541935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * dalvik.system.BaseDexClassLoader. We modify its 3551935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * dalvik.system.DexPathList pathList field to append additional DEX 3561935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * file entries. 3571935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 3581935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Field pathListField = findField(loader, "pathList"); 3591935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov Object dexPathList = pathListField.get(loader); 3601935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, 3611935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov new ArrayList<File>(additionalClassPathEntries), optimizedDirectory)); 3621935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov } 3631935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov 3641935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov /** 3651935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * A wrapper around 3661935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov * {@code private static final dalvik.system.DexPathList#makeDexElements}. 3671935ed3af7c6545bc38adfdc6026d87a3249222fSvetoslav Ganov */ 36844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static Object[] makeDexElements( 36944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Object dexPathList, ArrayList<File> files, File optimizedDirectory) 37044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throws IllegalAccessException, InvocationTargetException, 37144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn NoSuchMethodException { 37244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Method makeDexElements = 37344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); 37444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 37544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); 37644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 37744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 37844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 37944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /** 38044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * Installer for platform versions 4 to 13. 38144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 38244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static final class V4 { 38344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn private static void install(ClassLoader loader, List<File> additionalClassPathEntries) 38444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn throws IllegalArgumentException, IllegalAccessException, 38544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn NoSuchFieldException, IOException { 38644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn /* The patched class loader is expected to be a descendant of 38744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * dalvik.system.DexClassLoader. We modify its 38844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * fields mPaths, mFiles, mZips and mDexs to append additional DEX 38944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn * file entries. 39044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn */ 39144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn int extraSize = additionalClassPathEntries.size(); 39244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 39344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn Field pathField = findField(loader, "path"); 39444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 39544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn StringBuilder path = new StringBuilder((String) pathField.get(loader)); 39644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn String[] extraPaths = new String[extraSize]; 39744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn File[] extraFiles = new File[extraSize]; 39844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn ZipFile[] extraZips = new ZipFile[extraSize]; 39944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn DexFile[] extraDexs = new DexFile[extraSize]; 40044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn for (ListIterator<File> iterator = additionalClassPathEntries.listIterator(); 40144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn iterator.hasNext();) { 40244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn File additionalEntry = iterator.next(); 40344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn String entryPath = additionalEntry.getAbsolutePath(); 40444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn path.append(':').append(entryPath); 40544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn int index = iterator.previousIndex(); 40644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn extraPaths[index] = entryPath; 40744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn extraFiles[index] = additionalEntry; 40844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn extraZips[index] = new ZipFile(additionalEntry); 40944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0); 41044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 41144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 41244e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn pathField.set(loader, path.toString()); 41344e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn expandFieldArray(loader, "mPaths", extraPaths); 41444e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn expandFieldArray(loader, "mFiles", extraFiles); 41544e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn expandFieldArray(loader, "mZips", extraZips); 41644e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn expandFieldArray(loader, "mDexs", extraDexs); 41744e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 41844e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn } 41944e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn 42044e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn} 42144e3a52be44809a8018fb170e3130cc0ae164366Dianne Hackborn