[go: nahoru, domu]

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