MultiDexExtractor.java revision 7e267a38525afac2a571da186e770a2b86a01846
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
197e267a38525afac2a571da186e770a2b86a01846Maurice Chuimport android.content.Context;
207e267a38525afac2a571da186e770a2b86a01846Maurice Chuimport android.content.SharedPreferences;
211f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chuimport android.content.pm.ApplicationInfo;
221f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chuimport android.util.Log;
231f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu
24a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chuimport java.io.BufferedOutputStream;
25667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.Closeable;
26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File;
27d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport java.io.FileFilter;
28667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileNotFoundException;
29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileOutputStream;
30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException;
31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.InputStream;
327e267a38525afac2a571da186e770a2b86a01846Maurice Chuimport java.lang.reflect.InvocationTargetException;
337e267a38525afac2a571da186e770a2b86a01846Maurice Chuimport java.lang.reflect.Method;
34667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList;
35667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List;
36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipEntry;
37f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chuimport java.util.zip.ZipException;
38667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipFile;
39667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipOutputStream;
40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
41667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/**
42667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Exposes application secondary dex files as files in the application data
43667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory.
44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */
45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chufinal class MultiDexExtractor {
46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
47667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String TAG = MultiDex.TAG;
48667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
49667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
50667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * We look for additional dex files named {@code classes2.dex},
51667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * {@code classes3.dex}, etc.
52667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
53667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String DEX_PREFIX = "classes";
54667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String DEX_SUFFIX = ".dex";
55667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
56667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String EXTRACTED_NAME_EXT = ".classes";
57667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String EXTRACTED_SUFFIX = ".zip";
58f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static final int MAX_EXTRACT_ATTEMPTS = 3;
59667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
60667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final int BUFFER_SIZE = 0x4000;
61667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
627e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static final String PREFS_FILE = "multidex.version";
637e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static final String KEY_APK_SIZE = "apk_size";
647e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static final String KEY_NUM_DEX_FILES = "num_dex";
657e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static final String KEY_PREFIX_DEX_CRC = "crc";
667e267a38525afac2a571da186e770a2b86a01846Maurice Chu
67667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
68667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * Extracts application secondary dexes into files in the application data
69667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * directory.
70667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *
71667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * @return a list of files that were created. The list may be empty if there
72667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *         are no secondary dex files.
73667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * @throws IOException if encounters a problem while reading or writing
74667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *         secondary dex files
75667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
767e267a38525afac2a571da186e770a2b86a01846Maurice Chu    static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,
777e267a38525afac2a571da186e770a2b86a01846Maurice Chu            boolean forceReload) throws IOException {
78cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu        Log.i(TAG, "load(" + applicationInfo.sourceDir + ", forceReload=" + forceReload + ")");
797e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final File sourceApk = new File(applicationInfo.sourceDir);
807e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final long currentApkSize = sourceApk.length();
817e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final boolean isNewApk = getStoredApkSize(context) != currentApkSize;
827e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
83667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
847e267a38525afac2a571da186e770a2b86a01846Maurice Chu        prepareDexDir(dexDir, extractedFilePrefix, isNewApk);
85667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
86667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        final List<File> files = new ArrayList<File>();
877e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final ZipFile apk = new ZipFile(applicationInfo.sourceDir);
887e267a38525afac2a571da186e770a2b86a01846Maurice Chu
897e267a38525afac2a571da186e770a2b86a01846Maurice Chu        // If the CRC of any of the dex files is different than what we have stored or the number of
907e267a38525afac2a571da186e770a2b86a01846Maurice Chu        // dex files are different, then force reload everything.
917e267a38525afac2a571da186e770a2b86a01846Maurice Chu        ArrayList<Long> dexCrcs = getAllDexCrcs(apk);
927e267a38525afac2a571da186e770a2b86a01846Maurice Chu        if (isAnyDexCrcDifferent(context, dexCrcs)) {
937e267a38525afac2a571da186e770a2b86a01846Maurice Chu            forceReload = true;
947e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
95667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
96667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
97667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            int secondaryNumber = 2;
98667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
99667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
100667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            while (dexFile != null) {
101667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
102667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                File extractedFile = new File(dexDir, fileName);
103667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                files.add(extractedFile);
104667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
10588117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel                Log.i(TAG, "Need extracted file " + extractedFile);
106cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu                if (forceReload || !extractedFile.isFile()) {
10788117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel                    Log.i(TAG, "Extraction is needed for file " + extractedFile);
108f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    int numAttempts = 0;
109f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    boolean isExtractionSuccessful = false;
110f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
111f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        numAttempts++;
112f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
113f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // Create a zip file (extractedFile) containing only the secondary dex file
114f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // (dexFile) from the apk.
1157e267a38525afac2a571da186e770a2b86a01846Maurice Chu                        extract(apk, dexFile, extractedFile, extractedFilePrefix);
116f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
117f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // Verify that the extracted file is indeed a zip file.
118f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        isExtractionSuccessful = verifyZipFile(extractedFile);
119f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
120f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        // Log the sha1 of the extracted zip file
121f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +
12248cd040f1cf11d716f909f28ee237df6699a0f3bMaurice Chu                                " - length " + extractedFile.getAbsolutePath() + ": " +
12348cd040f1cf11d716f909f28ee237df6699a0f3bMaurice Chu                                extractedFile.length());
124f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        if (!isExtractionSuccessful) {
125f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                            // Delete the extracted file
126f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                            extractedFile.delete();
127f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        }
128f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    }
1297e267a38525afac2a571da186e770a2b86a01846Maurice Chu                    if (isExtractionSuccessful) {
1307e267a38525afac2a571da186e770a2b86a01846Maurice Chu                        // Write the current apk size and the dex crc's into the shared preferences
1317e267a38525afac2a571da186e770a2b86a01846Maurice Chu                        putStoredApkSize(context, currentApkSize);
1327e267a38525afac2a571da186e770a2b86a01846Maurice Chu                        putStoredDexCrcs(context, dexCrcs);
1337e267a38525afac2a571da186e770a2b86a01846Maurice Chu                    } else {
134f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        throw new IOException("Could not create zip file " +
135f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                                extractedFile.getAbsolutePath() + " for secondary dex (" +
136f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                                secondaryNumber + ")");
137f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    }
13888117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel                } else {
1397e267a38525afac2a571da186e770a2b86a01846Maurice Chu                    Log.i(TAG, "No extraction needed for " + extractedFile + " of size " +
1407e267a38525afac2a571da186e770a2b86a01846Maurice Chu                            extractedFile.length());
141667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                }
142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                secondaryNumber++;
143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } finally {
146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            try {
147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                apk.close();
148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            } catch (IOException e) {
149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                Log.w(TAG, "Failed to close resource", e);
150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
151667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
152667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        return files;
154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
156cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu    /**
1577e267a38525afac2a571da186e770a2b86a01846Maurice Chu     * Iterate through the expected dex files, classes.dex, classes2.dex, classes3.dex, etc. and
1587e267a38525afac2a571da186e770a2b86a01846Maurice Chu     * return the CRC of each zip entry in a list.
1597e267a38525afac2a571da186e770a2b86a01846Maurice Chu     */
1607e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static ArrayList<Long> getAllDexCrcs(ZipFile apk) {
1617e267a38525afac2a571da186e770a2b86a01846Maurice Chu        ArrayList<Long> dexCrcs = new ArrayList<Long>();
1627e267a38525afac2a571da186e770a2b86a01846Maurice Chu
1637e267a38525afac2a571da186e770a2b86a01846Maurice Chu        // Add the first one
1647e267a38525afac2a571da186e770a2b86a01846Maurice Chu        dexCrcs.add(apk.getEntry(DEX_PREFIX + DEX_SUFFIX).getCrc());
1657e267a38525afac2a571da186e770a2b86a01846Maurice Chu
1667e267a38525afac2a571da186e770a2b86a01846Maurice Chu        // Get the number of dex files in the apk.
1677e267a38525afac2a571da186e770a2b86a01846Maurice Chu        int secondaryNumber = 2;
1687e267a38525afac2a571da186e770a2b86a01846Maurice Chu        while (true) {
1697e267a38525afac2a571da186e770a2b86a01846Maurice Chu            ZipEntry dexEntry = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
1707e267a38525afac2a571da186e770a2b86a01846Maurice Chu            if (dexEntry == null) {
1717e267a38525afac2a571da186e770a2b86a01846Maurice Chu                break;
1727e267a38525afac2a571da186e770a2b86a01846Maurice Chu            }
1737e267a38525afac2a571da186e770a2b86a01846Maurice Chu
1747e267a38525afac2a571da186e770a2b86a01846Maurice Chu            dexCrcs.add(dexEntry.getCrc());
1757e267a38525afac2a571da186e770a2b86a01846Maurice Chu            secondaryNumber++;
1767e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
1777e267a38525afac2a571da186e770a2b86a01846Maurice Chu        return dexCrcs;
1787e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
1797e267a38525afac2a571da186e770a2b86a01846Maurice Chu
1807e267a38525afac2a571da186e770a2b86a01846Maurice Chu    /**
1817e267a38525afac2a571da186e770a2b86a01846Maurice Chu     * Returns true if the number of dex files is different than what is stored in the shared
1827e267a38525afac2a571da186e770a2b86a01846Maurice Chu     * preferences file or if any dex CRC value is different.
1837e267a38525afac2a571da186e770a2b86a01846Maurice Chu     */
1847e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static boolean isAnyDexCrcDifferent(Context context, ArrayList<Long> dexCrcs) {
1857e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final ArrayList<Long> storedDexCrcs = getStoredDexCrcs(context);
1867e267a38525afac2a571da186e770a2b86a01846Maurice Chu
1877e267a38525afac2a571da186e770a2b86a01846Maurice Chu        if (dexCrcs.size() != storedDexCrcs.size()) {
1887e267a38525afac2a571da186e770a2b86a01846Maurice Chu            return true;
1897e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
1907e267a38525afac2a571da186e770a2b86a01846Maurice Chu
1917e267a38525afac2a571da186e770a2b86a01846Maurice Chu        // We know the length of storedDexCrcs and dexCrcs are the same.
1927e267a38525afac2a571da186e770a2b86a01846Maurice Chu        for (int i = 0; i < storedDexCrcs.size(); i++) {
1937e267a38525afac2a571da186e770a2b86a01846Maurice Chu            if (storedDexCrcs.get(i) != dexCrcs.get(i)) {
1947e267a38525afac2a571da186e770a2b86a01846Maurice Chu                return true;
1957e267a38525afac2a571da186e770a2b86a01846Maurice Chu            }
1967e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
1977e267a38525afac2a571da186e770a2b86a01846Maurice Chu
1987e267a38525afac2a571da186e770a2b86a01846Maurice Chu        // All the same
1997e267a38525afac2a571da186e770a2b86a01846Maurice Chu        return false;
2007e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
2017e267a38525afac2a571da186e770a2b86a01846Maurice Chu
2027e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static long getStoredApkSize(Context context) {
2037e267a38525afac2a571da186e770a2b86a01846Maurice Chu        SharedPreferences prefs = context.getSharedPreferences(PREFS_FILE, 0);
2047e267a38525afac2a571da186e770a2b86a01846Maurice Chu        return prefs.getLong(KEY_APK_SIZE, -1L);
2057e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
2067e267a38525afac2a571da186e770a2b86a01846Maurice Chu
2077e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static void putStoredApkSize(Context context, long apkSize) {
2087e267a38525afac2a571da186e770a2b86a01846Maurice Chu        SharedPreferences prefs = context.getSharedPreferences(PREFS_FILE, 0);
2097e267a38525afac2a571da186e770a2b86a01846Maurice Chu        SharedPreferences.Editor edit = prefs.edit();
2107e267a38525afac2a571da186e770a2b86a01846Maurice Chu        edit.putLong(KEY_APK_SIZE, apkSize);
2117e267a38525afac2a571da186e770a2b86a01846Maurice Chu        apply(edit);
2127e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
2137e267a38525afac2a571da186e770a2b86a01846Maurice Chu
2147e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static ArrayList<Long> getStoredDexCrcs(Context context) {
2157e267a38525afac2a571da186e770a2b86a01846Maurice Chu        SharedPreferences prefs = context.getSharedPreferences(PREFS_FILE, 0);
2167e267a38525afac2a571da186e770a2b86a01846Maurice Chu        int numDexFiles = prefs.getInt(KEY_NUM_DEX_FILES, 0);
2177e267a38525afac2a571da186e770a2b86a01846Maurice Chu        ArrayList<Long> dexCrcs = new ArrayList<Long>(numDexFiles);
2187e267a38525afac2a571da186e770a2b86a01846Maurice Chu        for (int i = 0; i < numDexFiles; i++) {
2197e267a38525afac2a571da186e770a2b86a01846Maurice Chu            dexCrcs.add(prefs.getLong(makeDexCrcKey(i), 0));
2207e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
2217e267a38525afac2a571da186e770a2b86a01846Maurice Chu        return dexCrcs;
2227e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
2237e267a38525afac2a571da186e770a2b86a01846Maurice Chu
2247e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static void putStoredDexCrcs(Context context, ArrayList<Long> dexCrcs) {
2257e267a38525afac2a571da186e770a2b86a01846Maurice Chu        SharedPreferences prefs = context.getSharedPreferences(PREFS_FILE, 0);
2267e267a38525afac2a571da186e770a2b86a01846Maurice Chu        SharedPreferences.Editor edit = prefs.edit();
2277e267a38525afac2a571da186e770a2b86a01846Maurice Chu        edit.putInt(KEY_NUM_DEX_FILES, dexCrcs.size());
2287e267a38525afac2a571da186e770a2b86a01846Maurice Chu        for (int i = 0; i < dexCrcs.size(); i++) {
2297e267a38525afac2a571da186e770a2b86a01846Maurice Chu            edit.putLong(makeDexCrcKey(i), dexCrcs.get(i));
2307e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
2317e267a38525afac2a571da186e770a2b86a01846Maurice Chu        apply(edit);
2327e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
2337e267a38525afac2a571da186e770a2b86a01846Maurice Chu
2347e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static String makeDexCrcKey(int i) {
2357e267a38525afac2a571da186e770a2b86a01846Maurice Chu        return KEY_PREFIX_DEX_CRC + Integer.toString(i);
2367e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
2377e267a38525afac2a571da186e770a2b86a01846Maurice Chu
2387e267a38525afac2a571da186e770a2b86a01846Maurice Chu    /**
2397e267a38525afac2a571da186e770a2b86a01846Maurice Chu     * This always removes extraneous files.  If {@code removeAll} is true, then any existing
2407e267a38525afac2a571da186e770a2b86a01846Maurice Chu     * zip and dex files are removed from the secondary-dexes directory.
241cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu     */
242d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel    private static void prepareDexDir(File dexDir, final String extractedFilePrefix,
2437e267a38525afac2a571da186e770a2b86a01846Maurice Chu            final boolean removeAll) throws IOException {
244667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        dexDir.mkdir();
245667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        if (!dexDir.isDirectory()) {
246667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            throw new IOException("Failed to create dex directory " + dexDir.getPath());
247667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
248667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
249667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        // Clean possible old files
250d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel        FileFilter filter = new FileFilter() {
251d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel
252667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            @Override
253d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel            public boolean accept(File pathname) {
2547e267a38525afac2a571da186e770a2b86a01846Maurice Chu                return removeAll || !pathname.getName().startsWith(extractedFilePrefix);
255667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
256667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        };
257667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        File[] files = dexDir.listFiles(filter);
258667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        if (files == null) {
259667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
260667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            return;
261667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
262667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        for (File oldFile : files) {
2637e267a38525afac2a571da186e770a2b86a01846Maurice Chu            Log.w(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " +
2647e267a38525afac2a571da186e770a2b86a01846Maurice Chu                    oldFile.length());
265667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            if (!oldFile.delete()) {
266667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
26788117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel            } else {
26888117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel                Log.w(TAG, "Deleted old file " + oldFile.getPath());
269667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
270667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
271667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
272667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
27352eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel    private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,
2747e267a38525afac2a571da186e770a2b86a01846Maurice Chu            String extractedFilePrefix) throws IOException, FileNotFoundException {
275667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
276667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        InputStream in = apk.getInputStream(dexFile);
277667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        ZipOutputStream out = null;
278667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX,
279667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                extractTo.getParentFile());
280667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        Log.i(TAG, "Extracting " + tmp.getPath());
281667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
282a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu            out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
283667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            try {
284667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                ZipEntry classesDex = new ZipEntry("classes.dex");
285edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                // keep zip entry time since it is the criteria used by Dalvik
286edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                classesDex.setTime(dexFile.getTime());
287667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                out.putNextEntry(classesDex);
288667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
289667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                byte[] buffer = new byte[BUFFER_SIZE];
290667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                int length = in.read(buffer);
2911f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu                while (length != -1) {
29252eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel                    out.write(buffer, 0, length);
293667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                    length = in.read(buffer);
294667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                }
295a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu                out.closeEntry();
296667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            } finally {
297a0c1a85f60a44ef1c0592821270c20e988c64370Maurice Chu                out.close();
298667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
299667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.i(TAG, "Renaming to " + extractTo.getPath());
300a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu            if (!tmp.renameTo(extractTo)) {
301a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu                throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() +
302a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu                        "\" to \"" + extractTo.getAbsolutePath() + "\"");
303667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
304667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } finally {
305667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeQuietly(in);
306667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            tmp.delete(); // return status ignored
307667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
308667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
309667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
310667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
311f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     * Returns whether the file is a valid zip file.
312f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     */
313cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu    static boolean verifyZipFile(File file) {
314f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        try {
315f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            ZipFile zipFile = new ZipFile(file);
316f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            try {
317f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                zipFile.close();
318a0c1a85f60a44ef1c0592821270c20e988c64370Maurice Chu                return true;
319f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            } catch (IOException e) {
320f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath());
321f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            }
322f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } catch (ZipException ex) {
323f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", ex);
324f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } catch (IOException ex) {
325f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(), ex);
326f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        }
327f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        return false;
328f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    }
329f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
330f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    /**
331667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * Closes the given {@code Closeable}. Suppresses any IO exceptions.
332667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
333667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static void closeQuietly(Closeable closeable) {
334667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
335667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeable.close();
336667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } catch (IOException e) {
337667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.w(TAG, "Failed to close resource", e);
338667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
339667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
3407e267a38525afac2a571da186e770a2b86a01846Maurice Chu
3417e267a38525afac2a571da186e770a2b86a01846Maurice Chu    // The following is taken from SharedPreferencesCompat to avoid having a dependency of the
3427e267a38525afac2a571da186e770a2b86a01846Maurice Chu    // multidex support library on another support library.
3437e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static Method sApplyMethod;  // final
3447e267a38525afac2a571da186e770a2b86a01846Maurice Chu    static {
3457e267a38525afac2a571da186e770a2b86a01846Maurice Chu        try {
3467e267a38525afac2a571da186e770a2b86a01846Maurice Chu            Class cls = SharedPreferences.Editor.class;
3477e267a38525afac2a571da186e770a2b86a01846Maurice Chu            sApplyMethod = cls.getMethod("apply");
3487e267a38525afac2a571da186e770a2b86a01846Maurice Chu        } catch (NoSuchMethodException unused) {
3497e267a38525afac2a571da186e770a2b86a01846Maurice Chu            sApplyMethod = null;
3507e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
3517e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
3527e267a38525afac2a571da186e770a2b86a01846Maurice Chu
3537e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static void apply(SharedPreferences.Editor editor) {
3547e267a38525afac2a571da186e770a2b86a01846Maurice Chu        if (sApplyMethod != null) {
3557e267a38525afac2a571da186e770a2b86a01846Maurice Chu            try {
3567e267a38525afac2a571da186e770a2b86a01846Maurice Chu                sApplyMethod.invoke(editor);
3577e267a38525afac2a571da186e770a2b86a01846Maurice Chu                return;
3587e267a38525afac2a571da186e770a2b86a01846Maurice Chu            } catch (InvocationTargetException unused) {
3597e267a38525afac2a571da186e770a2b86a01846Maurice Chu                // fall through
3607e267a38525afac2a571da186e770a2b86a01846Maurice Chu            } catch (IllegalAccessException unused) {
3617e267a38525afac2a571da186e770a2b86a01846Maurice Chu                // fall through
3627e267a38525afac2a571da186e770a2b86a01846Maurice Chu            }
3637e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
3647e267a38525afac2a571da186e770a2b86a01846Maurice Chu        editor.commit();
3657e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
366667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu}
367