MultiDexExtractor.java revision 994fa84b3af07700fd5dcd477c02a6bd824dde45
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 61667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final int BUFFER_SIZE = 0x4000; 62667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 637e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static final String PREFS_FILE = "multidex.version"; 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 String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; 81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 82994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that 83994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // contains a secondary dex file in there is not consistent with the latest apk. Otherwise, 84994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // multi-process race conditions can cause a crash loop where one process deletes the zip 85994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // while another had created it. 86994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu prepareDexDir(dexDir, extractedFilePrefix); 87667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 88667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu final List<File> files = new ArrayList<File>(); 897e267a38525afac2a571da186e770a2b86a01846Maurice Chu final ZipFile apk = new ZipFile(applicationInfo.sourceDir); 907e267a38525afac2a571da186e770a2b86a01846Maurice Chu 917e267a38525afac2a571da186e770a2b86a01846Maurice Chu // If the CRC of any of the dex files is different than what we have stored or the number of 927e267a38525afac2a571da186e770a2b86a01846Maurice Chu // dex files are different, then force reload everything. 937e267a38525afac2a571da186e770a2b86a01846Maurice Chu ArrayList<Long> dexCrcs = getAllDexCrcs(apk); 947e267a38525afac2a571da186e770a2b86a01846Maurice Chu if (isAnyDexCrcDifferent(context, dexCrcs)) { 957e267a38525afac2a571da186e770a2b86a01846Maurice Chu forceReload = true; 967e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 97667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 98667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 99667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int secondaryNumber = 2; 100667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 101667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 102667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu while (dexFile != null) { 103667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; 104667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File extractedFile = new File(dexDir, fileName); 105667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu files.add(extractedFile); 106667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 10788117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel Log.i(TAG, "Need extracted file " + extractedFile); 108cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu if (forceReload || !extractedFile.isFile()) { 10988117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel Log.i(TAG, "Extraction is needed for file " + extractedFile); 110f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu int numAttempts = 0; 111f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu boolean isExtractionSuccessful = false; 112f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { 113f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu numAttempts++; 114f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu 115f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu // Create a zip file (extractedFile) containing only the secondary dex file 116f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu // (dexFile) from the apk. 1177e267a38525afac2a571da186e770a2b86a01846Maurice Chu extract(apk, dexFile, extractedFile, extractedFilePrefix); 118f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu 119f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu // Verify that the extracted file is indeed a zip file. 120f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu isExtractionSuccessful = verifyZipFile(extractedFile); 121f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu 122f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu // Log the sha1 of the extracted zip file 123f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") + 12448cd040f1cf11d716f909f28ee237df6699a0f3bMaurice Chu " - length " + extractedFile.getAbsolutePath() + ": " + 12548cd040f1cf11d716f909f28ee237df6699a0f3bMaurice Chu extractedFile.length()); 126f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu if (!isExtractionSuccessful) { 127f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu // Delete the extracted file 128f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu extractedFile.delete(); 129f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 130f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 1317e267a38525afac2a571da186e770a2b86a01846Maurice Chu if (isExtractionSuccessful) { 132994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // Write the dex crc's into the shared preferences 1337e267a38525afac2a571da186e770a2b86a01846Maurice Chu putStoredDexCrcs(context, dexCrcs); 1347e267a38525afac2a571da186e770a2b86a01846Maurice Chu } else { 135f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu throw new IOException("Could not create zip file " + 136f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu extractedFile.getAbsolutePath() + " for secondary dex (" + 137f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu secondaryNumber + ")"); 138f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 13988117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel } else { 1407e267a38525afac2a571da186e770a2b86a01846Maurice Chu Log.i(TAG, "No extraction needed for " + extractedFile + " of size " + 1417e267a38525afac2a571da186e770a2b86a01846Maurice Chu extractedFile.length()); 142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu secondaryNumber++; 144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu apk.close(); 149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 151667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 152667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return files; 155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 156667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 157cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu /** 1587e267a38525afac2a571da186e770a2b86a01846Maurice Chu * Iterate through the expected dex files, classes.dex, classes2.dex, classes3.dex, etc. and 1597e267a38525afac2a571da186e770a2b86a01846Maurice Chu * return the CRC of each zip entry in a list. 1607e267a38525afac2a571da186e770a2b86a01846Maurice Chu */ 1617e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static ArrayList<Long> getAllDexCrcs(ZipFile apk) { 1627e267a38525afac2a571da186e770a2b86a01846Maurice Chu ArrayList<Long> dexCrcs = new ArrayList<Long>(); 1637e267a38525afac2a571da186e770a2b86a01846Maurice Chu 1647e267a38525afac2a571da186e770a2b86a01846Maurice Chu // Add the first one 1657e267a38525afac2a571da186e770a2b86a01846Maurice Chu dexCrcs.add(apk.getEntry(DEX_PREFIX + DEX_SUFFIX).getCrc()); 1667e267a38525afac2a571da186e770a2b86a01846Maurice Chu 1677e267a38525afac2a571da186e770a2b86a01846Maurice Chu // Get the number of dex files in the apk. 1687e267a38525afac2a571da186e770a2b86a01846Maurice Chu int secondaryNumber = 2; 1697e267a38525afac2a571da186e770a2b86a01846Maurice Chu while (true) { 1707e267a38525afac2a571da186e770a2b86a01846Maurice Chu ZipEntry dexEntry = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 1717e267a38525afac2a571da186e770a2b86a01846Maurice Chu if (dexEntry == null) { 1727e267a38525afac2a571da186e770a2b86a01846Maurice Chu break; 1737e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 1747e267a38525afac2a571da186e770a2b86a01846Maurice Chu 1757e267a38525afac2a571da186e770a2b86a01846Maurice Chu dexCrcs.add(dexEntry.getCrc()); 1767e267a38525afac2a571da186e770a2b86a01846Maurice Chu secondaryNumber++; 1777e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 1787e267a38525afac2a571da186e770a2b86a01846Maurice Chu return dexCrcs; 1797e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 1807e267a38525afac2a571da186e770a2b86a01846Maurice Chu 1817e267a38525afac2a571da186e770a2b86a01846Maurice Chu /** 1827e267a38525afac2a571da186e770a2b86a01846Maurice Chu * Returns true if the number of dex files is different than what is stored in the shared 1837e267a38525afac2a571da186e770a2b86a01846Maurice Chu * preferences file or if any dex CRC value is different. 1847e267a38525afac2a571da186e770a2b86a01846Maurice Chu */ 1857e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static boolean isAnyDexCrcDifferent(Context context, ArrayList<Long> dexCrcs) { 1867e267a38525afac2a571da186e770a2b86a01846Maurice Chu final ArrayList<Long> storedDexCrcs = getStoredDexCrcs(context); 1877e267a38525afac2a571da186e770a2b86a01846Maurice Chu 1887e267a38525afac2a571da186e770a2b86a01846Maurice Chu if (dexCrcs.size() != storedDexCrcs.size()) { 1897e267a38525afac2a571da186e770a2b86a01846Maurice Chu return true; 1907e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 1917e267a38525afac2a571da186e770a2b86a01846Maurice Chu 1927e267a38525afac2a571da186e770a2b86a01846Maurice Chu // We know the length of storedDexCrcs and dexCrcs are the same. 1937e267a38525afac2a571da186e770a2b86a01846Maurice Chu for (int i = 0; i < storedDexCrcs.size(); i++) { 1947e267a38525afac2a571da186e770a2b86a01846Maurice Chu if (storedDexCrcs.get(i) != dexCrcs.get(i)) { 1957e267a38525afac2a571da186e770a2b86a01846Maurice Chu return true; 1967e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 1977e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 1987e267a38525afac2a571da186e770a2b86a01846Maurice Chu 1997e267a38525afac2a571da186e770a2b86a01846Maurice Chu // All the same 2007e267a38525afac2a571da186e770a2b86a01846Maurice Chu return false; 2017e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 2027e267a38525afac2a571da186e770a2b86a01846Maurice Chu 2037e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static ArrayList<Long> getStoredDexCrcs(Context context) { 204994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu SharedPreferences prefs = getMultiDexPreferences(context); 2057e267a38525afac2a571da186e770a2b86a01846Maurice Chu int numDexFiles = prefs.getInt(KEY_NUM_DEX_FILES, 0); 2067e267a38525afac2a571da186e770a2b86a01846Maurice Chu ArrayList<Long> dexCrcs = new ArrayList<Long>(numDexFiles); 2077e267a38525afac2a571da186e770a2b86a01846Maurice Chu for (int i = 0; i < numDexFiles; i++) { 2087e267a38525afac2a571da186e770a2b86a01846Maurice Chu dexCrcs.add(prefs.getLong(makeDexCrcKey(i), 0)); 2097e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 2107e267a38525afac2a571da186e770a2b86a01846Maurice Chu return dexCrcs; 2117e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 2127e267a38525afac2a571da186e770a2b86a01846Maurice Chu 2137e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static void putStoredDexCrcs(Context context, ArrayList<Long> dexCrcs) { 214994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu SharedPreferences prefs = getMultiDexPreferences(context); 2157e267a38525afac2a571da186e770a2b86a01846Maurice Chu SharedPreferences.Editor edit = prefs.edit(); 2167e267a38525afac2a571da186e770a2b86a01846Maurice Chu edit.putInt(KEY_NUM_DEX_FILES, dexCrcs.size()); 2177e267a38525afac2a571da186e770a2b86a01846Maurice Chu for (int i = 0; i < dexCrcs.size(); i++) { 2187e267a38525afac2a571da186e770a2b86a01846Maurice Chu edit.putLong(makeDexCrcKey(i), dexCrcs.get(i)); 2197e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 2207e267a38525afac2a571da186e770a2b86a01846Maurice Chu apply(edit); 2217e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 2227e267a38525afac2a571da186e770a2b86a01846Maurice Chu 223994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu private static SharedPreferences getMultiDexPreferences(Context context) { 224994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu return context.getSharedPreferences(PREFS_FILE, 225994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB 226994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu ? Context.MODE_PRIVATE 227994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS); 228994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu } 229994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu 2307e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static String makeDexCrcKey(int i) { 2317e267a38525afac2a571da186e770a2b86a01846Maurice Chu return KEY_PREFIX_DEX_CRC + Integer.toString(i); 2327e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 2337e267a38525afac2a571da186e770a2b86a01846Maurice Chu 2347e267a38525afac2a571da186e770a2b86a01846Maurice Chu /** 235994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu * This removes any files that do not have the correct prefix. 236cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu */ 237994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu private static void prepareDexDir(File dexDir, final String extractedFilePrefix) 238994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu throws IOException { 239667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexDir.mkdir(); 240667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!dexDir.isDirectory()) { 241667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new IOException("Failed to create dex directory " + dexDir.getPath()); 242667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 243667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 244667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // Clean possible old files 245d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel FileFilter filter = new FileFilter() { 246d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel 247667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu @Override 248d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel public boolean accept(File pathname) { 249994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu return !pathname.getName().startsWith(extractedFilePrefix); 250667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 251667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu }; 252667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File[] files = dexDir.listFiles(filter); 253667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (files == null) { 254667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); 255667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 256667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 257667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (File oldFile : files) { 2587e267a38525afac2a571da186e770a2b86a01846Maurice Chu Log.w(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " + 2597e267a38525afac2a571da186e770a2b86a01846Maurice Chu oldFile.length()); 260667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!oldFile.delete()) { 261667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); 26288117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel } else { 26388117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel Log.w(TAG, "Deleted old file " + oldFile.getPath()); 264667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 265667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 266667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 267667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 26852eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, 2697e267a38525afac2a571da186e770a2b86a01846Maurice Chu String extractedFilePrefix) throws IOException, FileNotFoundException { 270667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 271667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu InputStream in = apk.getInputStream(dexFile); 272667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipOutputStream out = null; 273667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX, 274667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getParentFile()); 275667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Extracting " + tmp.getPath()); 276667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 277a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp))); 278667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 279667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry classesDex = new ZipEntry("classes.dex"); 280edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel // keep zip entry time since it is the criteria used by Dalvik 281edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel classesDex.setTime(dexFile.getTime()); 282667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out.putNextEntry(classesDex); 283667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 284667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu byte[] buffer = new byte[BUFFER_SIZE]; 285667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int length = in.read(buffer); 2861f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu while (length != -1) { 28752eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel out.write(buffer, 0, length); 288667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu length = in.read(buffer); 289667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 290a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu out.closeEntry(); 291667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 292a0c1a85f60a44ef1c0592821270c20e988c64370Maurice Chu out.close(); 293667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 294667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Renaming to " + extractTo.getPath()); 295a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu if (!tmp.renameTo(extractTo)) { 296a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + 297a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu "\" to \"" + extractTo.getAbsolutePath() + "\""); 298667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 299667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 300667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(in); 301667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu tmp.delete(); // return status ignored 302667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 303667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 304667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 305667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 306f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu * Returns whether the file is a valid zip file. 307f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu */ 308cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu static boolean verifyZipFile(File file) { 309f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu try { 310f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu ZipFile zipFile = new ZipFile(file); 311f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu try { 312f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu zipFile.close(); 313a0c1a85f60a44ef1c0592821270c20e988c64370Maurice Chu return true; 314f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } catch (IOException e) { 315f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath()); 316f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 317f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } catch (ZipException ex) { 318f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", ex); 319f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } catch (IOException ex) { 320f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(), ex); 321f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 322f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu return false; 323f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 324f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu 325f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu /** 326667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Closes the given {@code Closeable}. Suppresses any IO exceptions. 327667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 328667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void closeQuietly(Closeable closeable) { 329667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 330667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeable.close(); 331667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 332667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 333667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 334667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 3357e267a38525afac2a571da186e770a2b86a01846Maurice Chu 3367e267a38525afac2a571da186e770a2b86a01846Maurice Chu // The following is taken from SharedPreferencesCompat to avoid having a dependency of the 3377e267a38525afac2a571da186e770a2b86a01846Maurice Chu // multidex support library on another support library. 3387e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static Method sApplyMethod; // final 3397e267a38525afac2a571da186e770a2b86a01846Maurice Chu static { 3407e267a38525afac2a571da186e770a2b86a01846Maurice Chu try { 3417e267a38525afac2a571da186e770a2b86a01846Maurice Chu Class cls = SharedPreferences.Editor.class; 3427e267a38525afac2a571da186e770a2b86a01846Maurice Chu sApplyMethod = cls.getMethod("apply"); 3437e267a38525afac2a571da186e770a2b86a01846Maurice Chu } catch (NoSuchMethodException unused) { 3447e267a38525afac2a571da186e770a2b86a01846Maurice Chu sApplyMethod = null; 3457e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 3467e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 3477e267a38525afac2a571da186e770a2b86a01846Maurice Chu 3487e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static void apply(SharedPreferences.Editor editor) { 3497e267a38525afac2a571da186e770a2b86a01846Maurice Chu if (sApplyMethod != null) { 3507e267a38525afac2a571da186e770a2b86a01846Maurice Chu try { 3517e267a38525afac2a571da186e770a2b86a01846Maurice Chu sApplyMethod.invoke(editor); 3527e267a38525afac2a571da186e770a2b86a01846Maurice Chu return; 3537e267a38525afac2a571da186e770a2b86a01846Maurice Chu } catch (InvocationTargetException unused) { 3547e267a38525afac2a571da186e770a2b86a01846Maurice Chu // fall through 3557e267a38525afac2a571da186e770a2b86a01846Maurice Chu } catch (IllegalAccessException unused) { 3567e267a38525afac2a571da186e770a2b86a01846Maurice Chu // fall through 3577e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 3587e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 3597e267a38525afac2a571da186e770a2b86a01846Maurice Chu editor.commit(); 3607e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 361667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu} 362