MultiDexExtractor.java revision 602c6ca8cae4718ba8ff9f65e53305d002479359
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;
22994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chuimport android.os.Build;
231f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chuimport android.util.Log;
241f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu
25a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chuimport java.io.BufferedOutputStream;
26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.Closeable;
27667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File;
28d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport java.io.FileFilter;
29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileNotFoundException;
30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileOutputStream;
31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException;
32667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.InputStream;
337e267a38525afac2a571da186e770a2b86a01846Maurice Chuimport java.lang.reflect.InvocationTargetException;
347e267a38525afac2a571da186e770a2b86a01846Maurice Chuimport java.lang.reflect.Method;
35667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList;
36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List;
37667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipEntry;
38f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chuimport java.util.zip.ZipException;
39667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipFile;
40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipOutputStream;
41667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
42667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/**
43667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Exposes application secondary dex files as files in the application data
44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory.
45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */
46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chufinal class MultiDexExtractor {
47667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
48667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String TAG = MultiDex.TAG;
49667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
50667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
51667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * We look for additional dex files named {@code classes2.dex},
52667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * {@code classes3.dex}, etc.
53667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
54667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String DEX_PREFIX = "classes";
55667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String DEX_SUFFIX = ".dex";
56667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
57667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String EXTRACTED_NAME_EXT = ".classes";
58667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static final String EXTRACTED_SUFFIX = ".zip";
59f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    private static final int MAX_EXTRACT_ATTEMPTS = 3;
60667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
617e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static final String PREFS_FILE = "multidex.version";
62602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static final String KEY_TIME_STAMP = "timestamp";
63602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static final String KEY_CRC = "crc";
64602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static final String KEY_DEX_NUMBER = "dex.number";
65602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
66602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    /**
67602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel     * Size of reading buffers.
68602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel     */
69602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static final int BUFFER_SIZE = 0x4000;
70602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    /* Keep value away from 0 because it is a too probable time stamp value */
71602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static final long NO_VALUE = -1L;
727e267a38525afac2a571da186e770a2b86a01846Maurice Chu
73667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
74667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * Extracts application secondary dexes into files in the application data
75667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * directory.
76667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *
77667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * @return a list of files that were created. The list may be empty if there
78667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *         are no secondary dex files.
79667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * @throws IOException if encounters a problem while reading or writing
80667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     *         secondary dex files
81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
827e267a38525afac2a571da186e770a2b86a01846Maurice Chu    static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,
837e267a38525afac2a571da186e770a2b86a01846Maurice Chu            boolean forceReload) throws IOException {
84602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");
857e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final File sourceApk = new File(applicationInfo.sourceDir);
86602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
87602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        File archive = new File(applicationInfo.sourceDir);
88602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        long currentCrc = getZipCrc(archive);
89602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
90602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        List<File> files;
91602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        if (!forceReload && !isModified(context, archive, currentCrc)) {
92602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            try {
93602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                files = loadExistingExtractions(context, sourceApk, dexDir);
94602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            } catch (IOException ioe) {
95602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
96602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                        + " falling back to fresh extraction", ioe);
97602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                files = performExtractions(sourceApk, dexDir);
98602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
99602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
100602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            }
101602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        } else {
102602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            Log.i(TAG, "Detected that extraction must be performed.");
103602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            files = performExtractions(sourceApk, dexDir);
104602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
105602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        }
106602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
107602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        Log.i(TAG, "load found " + files.size() + " secondary dex files");
108602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        return files;
109602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    }
110602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
111602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir)
112602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            throws IOException {
113602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        Log.i(TAG, "loading existing secondary dex files");
114602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
115602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
116602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
117602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        final List<File> files = new ArrayList<File>(totalDexNumber);
118602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
119602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
120602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
121602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            File extractedFile = new File(dexDir, fileName);
122602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            if (extractedFile.isFile()) {
123602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                files.add(extractedFile);
124602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                if (!verifyZipFile(extractedFile)) {
125602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    Log.i(TAG, "Invalid zip file: " + extractedFile);
126602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    throw new IOException("Invalid ZIP file.");
127602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                }
128602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            } else {
129602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                throw new IOException("Missing extracted secondary dex file '" +
130602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                        extractedFile.getPath() + "'");
131602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            }
132602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        }
133602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
134602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        return files;
135602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    }
136602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
137602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static boolean isModified(Context context, File archive, long currentCrc) {
138602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        SharedPreferences prefs = getMultiDexPreferences(context);
139602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive))
140602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                || (prefs.getLong(KEY_CRC, NO_VALUE) != currentCrc);
141602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    }
142602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
143602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static long getTimeStamp(File archive) {
144602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        long timeStamp = archive.lastModified();
145602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        if (timeStamp == NO_VALUE) {
146602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            // never return NO_VALUE
147602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            timeStamp--;
148602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        }
149602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        return timeStamp;
150602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    }
151602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
152602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
153602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static long getZipCrc(File archive) throws IOException {
154602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        long computedValue = ZipUtil.getZipCrc(archive);
155602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        if (computedValue == NO_VALUE) {
156602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            // never return NO_VALUE
157602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            computedValue--;
158602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        }
159602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        return computedValue;
160602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    }
161602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
162602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static List<File> performExtractions(File sourceApk, File dexDir)
163602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            throws IOException {
164602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
1657e267a38525afac2a571da186e770a2b86a01846Maurice Chu        final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
166667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
167994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu        // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that
168994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu        // contains a secondary dex file in there is not consistent with the latest apk.  Otherwise,
169994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu        // multi-process race conditions can cause a crash loop where one process deletes the zip
170994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu        // while another had created it.
171994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu        prepareDexDir(dexDir, extractedFilePrefix);
172667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
173602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        List<File> files = new ArrayList<File>();
1747e267a38525afac2a571da186e770a2b86a01846Maurice Chu
175602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        final ZipFile apk = new ZipFile(sourceApk);
176667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
177667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
178667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            int secondaryNumber = 2;
179667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
180667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
181667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            while (dexFile != null) {
182667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
183667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                File extractedFile = new File(dexDir, fileName);
184667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                files.add(extractedFile);
185667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
186602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                Log.i(TAG, "Extraction is needed for file " + extractedFile);
187602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                int numAttempts = 0;
188602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                boolean isExtractionSuccessful = false;
189602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {
190602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    numAttempts++;
191602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
192602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    // Create a zip file (extractedFile) containing only the secondary dex file
193602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    // (dexFile) from the apk.
194602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    extract(apk, dexFile, extractedFile, extractedFilePrefix);
195602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
196602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    // Verify that the extracted file is indeed a zip file.
197602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    isExtractionSuccessful = verifyZipFile(extractedFile);
198602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel
199602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    // Log the sha1 of the extracted zip file
200602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +
201602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                            " - length " + extractedFile.getAbsolutePath() + ": " +
202602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                            extractedFile.length());
203602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    if (!isExtractionSuccessful) {
204602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                        // Delete the extracted file
205602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                        extractedFile.delete();
206602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                        if (extractedFile.exists()) {
207602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                            Log.w(TAG, "Failed to delete corrupted secondary dex '" +
208602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                                    extractedFile.getPath() + "'");
209f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                        }
210f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                    }
211602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                }
212602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                if (!isExtractionSuccessful) {
213602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                    throw new IOException("Could not create zip file " +
214602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                            extractedFile.getAbsolutePath() + " for secondary dex (" +
215602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel                            secondaryNumber + ")");
216667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                }
217667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                secondaryNumber++;
218667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
219667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
220667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } finally {
221667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            try {
222667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                apk.close();
223667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            } catch (IOException e) {
224667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                Log.w(TAG, "Failed to close resource", e);
225667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
226667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
227667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
228667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        return files;
229667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
230667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
231602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel    private static void putStoredApkInfo(Context context, long timeStamp, long crc,
232602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            int totalDexNumber) {
233994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu        SharedPreferences prefs = getMultiDexPreferences(context);
2347e267a38525afac2a571da186e770a2b86a01846Maurice Chu        SharedPreferences.Editor edit = prefs.edit();
235602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        edit.putLong(KEY_TIME_STAMP, timeStamp);
236602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        edit.putLong(KEY_CRC, crc);
237602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        /* SharedPreferences.Editor doc says that apply() and commit() "atomically performs the
238602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel         * requested modifications" it should be OK to rely on saving the dex files number (getting
239602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel         * old number value would go along with old crc and time stamp).
240602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel         */
241602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel        edit.putInt(KEY_DEX_NUMBER, totalDexNumber);
2427e267a38525afac2a571da186e770a2b86a01846Maurice Chu        apply(edit);
2437e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
2447e267a38525afac2a571da186e770a2b86a01846Maurice Chu
245994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu    private static SharedPreferences getMultiDexPreferences(Context context) {
246994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu        return context.getSharedPreferences(PREFS_FILE,
247994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu                Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
248994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu                        ? Context.MODE_PRIVATE
249994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu                        : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
250994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu    }
251994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu
2527e267a38525afac2a571da186e770a2b86a01846Maurice Chu    /**
253994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu     * This removes any files that do not have the correct prefix.
254cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu     */
255994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu    private static void prepareDexDir(File dexDir, final String extractedFilePrefix)
256994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu            throws IOException {
257667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        dexDir.mkdir();
258667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        if (!dexDir.isDirectory()) {
259667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            throw new IOException("Failed to create dex directory " + dexDir.getPath());
260667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
261667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
262667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        // Clean possible old files
263d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel        FileFilter filter = new FileFilter() {
264d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel
265667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            @Override
266d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel            public boolean accept(File pathname) {
267994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu                return !pathname.getName().startsWith(extractedFilePrefix);
268667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
269667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        };
270667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        File[] files = dexDir.listFiles(filter);
271667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        if (files == null) {
272667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
273667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            return;
274667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
275667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        for (File oldFile : files) {
2767e267a38525afac2a571da186e770a2b86a01846Maurice Chu            Log.w(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " +
2777e267a38525afac2a571da186e770a2b86a01846Maurice Chu                    oldFile.length());
278667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            if (!oldFile.delete()) {
279667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
28088117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel            } else {
28188117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel                Log.w(TAG, "Deleted old file " + oldFile.getPath());
282667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
283667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
284667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
285667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
28652eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel    private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,
2877e267a38525afac2a571da186e770a2b86a01846Maurice Chu            String extractedFilePrefix) throws IOException, FileNotFoundException {
288667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
289667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        InputStream in = apk.getInputStream(dexFile);
290667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        ZipOutputStream out = null;
291667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX,
292667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                extractTo.getParentFile());
293667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        Log.i(TAG, "Extracting " + tmp.getPath());
294667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
295a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu            out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
296667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            try {
297667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                ZipEntry classesDex = new ZipEntry("classes.dex");
298edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                // keep zip entry time since it is the criteria used by Dalvik
299edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                classesDex.setTime(dexFile.getTime());
300667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                out.putNextEntry(classesDex);
301667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
302667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                byte[] buffer = new byte[BUFFER_SIZE];
303667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                int length = in.read(buffer);
3041f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu                while (length != -1) {
30552eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel                    out.write(buffer, 0, length);
306667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                    length = in.read(buffer);
307667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                }
308a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu                out.closeEntry();
309667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            } finally {
310a0c1a85f60a44ef1c0592821270c20e988c64370Maurice Chu                out.close();
311667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
312667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.i(TAG, "Renaming to " + extractTo.getPath());
313a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu            if (!tmp.renameTo(extractTo)) {
314a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu                throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() +
315a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu                        "\" to \"" + extractTo.getAbsolutePath() + "\"");
316667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
317667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } finally {
318667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeQuietly(in);
319667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            tmp.delete(); // return status ignored
320667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
321667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
322667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
323667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
324f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     * Returns whether the file is a valid zip file.
325f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu     */
326cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu    static boolean verifyZipFile(File file) {
327f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        try {
328f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            ZipFile zipFile = new ZipFile(file);
329f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            try {
330f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                zipFile.close();
331a0c1a85f60a44ef1c0592821270c20e988c64370Maurice Chu                return true;
332f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            } catch (IOException e) {
333f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu                Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath());
334f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            }
335f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } catch (ZipException ex) {
336f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", ex);
337f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        } catch (IOException ex) {
338f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu            Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(), ex);
339f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        }
340f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu        return false;
341f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    }
342f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu
343f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu    /**
344667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * Closes the given {@code Closeable}. Suppresses any IO exceptions.
345667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
346667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static void closeQuietly(Closeable closeable) {
347667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
348667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeable.close();
349667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } catch (IOException e) {
350667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.w(TAG, "Failed to close resource", e);
351667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
352667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
3537e267a38525afac2a571da186e770a2b86a01846Maurice Chu
3547e267a38525afac2a571da186e770a2b86a01846Maurice Chu    // The following is taken from SharedPreferencesCompat to avoid having a dependency of the
3557e267a38525afac2a571da186e770a2b86a01846Maurice Chu    // multidex support library on another support library.
3567e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static Method sApplyMethod;  // final
3577e267a38525afac2a571da186e770a2b86a01846Maurice Chu    static {
3587e267a38525afac2a571da186e770a2b86a01846Maurice Chu        try {
359602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel            Class<?> cls = SharedPreferences.Editor.class;
3607e267a38525afac2a571da186e770a2b86a01846Maurice Chu            sApplyMethod = cls.getMethod("apply");
3617e267a38525afac2a571da186e770a2b86a01846Maurice Chu        } catch (NoSuchMethodException unused) {
3627e267a38525afac2a571da186e770a2b86a01846Maurice Chu            sApplyMethod = null;
3637e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
3647e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
3657e267a38525afac2a571da186e770a2b86a01846Maurice Chu
3667e267a38525afac2a571da186e770a2b86a01846Maurice Chu    private static void apply(SharedPreferences.Editor editor) {
3677e267a38525afac2a571da186e770a2b86a01846Maurice Chu        if (sApplyMethod != null) {
3687e267a38525afac2a571da186e770a2b86a01846Maurice Chu            try {
3697e267a38525afac2a571da186e770a2b86a01846Maurice Chu                sApplyMethod.invoke(editor);
3707e267a38525afac2a571da186e770a2b86a01846Maurice Chu                return;
3717e267a38525afac2a571da186e770a2b86a01846Maurice Chu            } catch (InvocationTargetException unused) {
3727e267a38525afac2a571da186e770a2b86a01846Maurice Chu                // fall through
3737e267a38525afac2a571da186e770a2b86a01846Maurice Chu            } catch (IllegalAccessException unused) {
3747e267a38525afac2a571da186e770a2b86a01846Maurice Chu                // fall through
3757e267a38525afac2a571da186e770a2b86a01846Maurice Chu            }
3767e267a38525afac2a571da186e770a2b86a01846Maurice Chu        }
3777e267a38525afac2a571da186e770a2b86a01846Maurice Chu        editor.commit();
3787e267a38525afac2a571da186e770a2b86a01846Maurice Chu    }
379667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu}
380