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