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; 24a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chuimport java.io.BufferedOutputStream; 25667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.Closeable; 26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File; 27d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport java.io.FileFilter; 28667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileNotFoundException; 29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileOutputStream; 30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException; 31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.InputStream; 32048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnsonimport java.io.RandomAccessFile; 33048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnsonimport java.nio.channels.FileChannel; 34048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnsonimport java.nio.channels.FileLock; 35667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList; 36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List; 37667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipEntry; 38667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipFile; 39667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipOutputStream; 40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 41667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/** 42667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Exposes application secondary dex files as files in the application data 43667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory. 44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chufinal class MultiDexExtractor { 46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 479958145a97586375966bc133c900ead0e1550f2aYohann Roussel /** 489958145a97586375966bc133c900ead0e1550f2aYohann Roussel * Zip file containing one secondary dex file. 499958145a97586375966bc133c900ead0e1550f2aYohann Roussel */ 509958145a97586375966bc133c900ead0e1550f2aYohann Roussel private static class ExtractedDex extends File { 519958145a97586375966bc133c900ead0e1550f2aYohann Roussel public long crc = NO_VALUE; 529958145a97586375966bc133c900ead0e1550f2aYohann Roussel 539958145a97586375966bc133c900ead0e1550f2aYohann Roussel public ExtractedDex(File dexDir, String fileName) { 549958145a97586375966bc133c900ead0e1550f2aYohann Roussel super(dexDir, fileName); 559958145a97586375966bc133c900ead0e1550f2aYohann Roussel } 569958145a97586375966bc133c900ead0e1550f2aYohann Roussel } 579958145a97586375966bc133c900ead0e1550f2aYohann Roussel 58667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String TAG = MultiDex.TAG; 59667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 60667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 61667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * We look for additional dex files named {@code classes2.dex}, 62667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code classes3.dex}, etc. 63667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 64667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String DEX_PREFIX = "classes"; 65667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String DEX_SUFFIX = ".dex"; 66667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 67667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String EXTRACTED_NAME_EXT = ".classes"; 68667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String EXTRACTED_SUFFIX = ".zip"; 69f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu private static final int MAX_EXTRACT_ATTEMPTS = 3; 70667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 717e267a38525afac2a571da186e770a2b86a01846Maurice Chu private static final String PREFS_FILE = "multidex.version"; 72602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static final String KEY_TIME_STAMP = "timestamp"; 73602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static final String KEY_CRC = "crc"; 74602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static final String KEY_DEX_NUMBER = "dex.number"; 759958145a97586375966bc133c900ead0e1550f2aYohann Roussel private static final String KEY_DEX_CRC = "dex.crc."; 769958145a97586375966bc133c900ead0e1550f2aYohann Roussel private static final String KEY_DEX_TIME = "dex.time."; 77602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 78602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel /** 79602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel * Size of reading buffers. 80602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel */ 81602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static final int BUFFER_SIZE = 0x4000; 82602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel /* Keep value away from 0 because it is a too probable time stamp value */ 83602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static final long NO_VALUE = -1L; 847e267a38525afac2a571da186e770a2b86a01846Maurice Chu 85048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson private static final String LOCK_FILENAME = "MultiDex.lock"; 86048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson 87667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 88667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Extracts application secondary dexes into files in the application data 89667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory. 90667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 91667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @return a list of files that were created. The list may be empty if there 92048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * are no secondary dex files. Never return null. 93667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws IOException if encounters a problem while reading or writing 94667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * secondary dex files 95667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 969958145a97586375966bc133c900ead0e1550f2aYohann Roussel static List<? extends File> load(Context context, ApplicationInfo applicationInfo, File dexDir, 977e267a38525afac2a571da186e770a2b86a01846Maurice Chu boolean forceReload) throws IOException { 98602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")"); 997e267a38525afac2a571da186e770a2b86a01846Maurice Chu final File sourceApk = new File(applicationInfo.sourceDir); 100602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 101590a07e63868f0a1da311ff22b4a9f35eb48a865Yohann Roussel long currentCrc = getZipCrc(sourceApk); 102602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 103048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson // Validity check and extraction must be done only while the lock file has been taken. 104048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson File lockFile = new File(dexDir, LOCK_FILENAME); 105048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson RandomAccessFile lockRaf = new RandomAccessFile(lockFile, "rw"); 106048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson FileChannel lockChannel = null; 107048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson FileLock cacheLock = null; 1089958145a97586375966bc133c900ead0e1550f2aYohann Roussel List<ExtractedDex> files; 109048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson IOException releaseLockException = null; 110048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson try { 111048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson lockChannel = lockRaf.getChannel(); 112048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson Log.i(TAG, "Blocking on lock " + lockFile.getPath()); 113048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson cacheLock = lockChannel.lock(); 114048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson Log.i(TAG, lockFile.getPath() + " locked"); 115048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson 116048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson if (!forceReload && !isModified(context, sourceApk, currentCrc)) { 117048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson try { 118048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson files = loadExistingExtractions(context, sourceApk, dexDir); 119048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } catch (IOException ioe) { 120048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson Log.w(TAG, "Failed to reload existing extracted secondary dex files," 121048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson + " falling back to fresh extraction", ioe); 122048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson files = performExtractions(sourceApk, dexDir); 1239958145a97586375966bc133c900ead0e1550f2aYohann Roussel putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files); 124048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } 125048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } else { 126048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson Log.i(TAG, "Detected that extraction must be performed."); 127602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel files = performExtractions(sourceApk, dexDir); 1289958145a97586375966bc133c900ead0e1550f2aYohann Roussel putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files); 129602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 130048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } finally { 131048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson if (cacheLock != null) { 132048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson try { 133048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson cacheLock.release(); 134048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } catch (IOException e) { 135048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson Log.e(TAG, "Failed to release lock on " + lockFile.getPath()); 136048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson // Exception while releasing the lock is bad, we want to report it, but not at 137048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson // the price of overriding any already pending exception. 138048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson releaseLockException = e; 139048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } 140048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } 141048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson if (lockChannel != null) { 142048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson closeQuietly(lockChannel); 143048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } 144048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson closeQuietly(lockRaf); 145048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson } 146048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson 147048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson if (releaseLockException != null) { 148048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson throw releaseLockException; 149602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 150602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 151602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel Log.i(TAG, "load found " + files.size() + " secondary dex files"); 152602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel return files; 153602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 154602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 155048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson /** 156048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * Load previously extracted secondary dex files. Should be called only while owning the lock on 157048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * {@link #LOCK_FILENAME}. 158048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson */ 1599958145a97586375966bc133c900ead0e1550f2aYohann Roussel private static List<ExtractedDex> loadExistingExtractions( 1609958145a97586375966bc133c900ead0e1550f2aYohann Roussel Context context, File sourceApk, File dexDir) 161602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel throws IOException { 162602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel Log.i(TAG, "loading existing secondary dex files"); 163602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 164602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; 1659958145a97586375966bc133c900ead0e1550f2aYohann Roussel SharedPreferences multiDexPreferences = getMultiDexPreferences(context); 1669958145a97586375966bc133c900ead0e1550f2aYohann Roussel int totalDexNumber = multiDexPreferences.getInt(KEY_DEX_NUMBER, 1); 1679958145a97586375966bc133c900ead0e1550f2aYohann Roussel final List<ExtractedDex> files = new ArrayList<ExtractedDex>(totalDexNumber - 1); 168602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 169602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { 170602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; 1719958145a97586375966bc133c900ead0e1550f2aYohann Roussel ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName); 172602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel if (extractedFile.isFile()) { 1739958145a97586375966bc133c900ead0e1550f2aYohann Roussel extractedFile.crc = getZipCrc(extractedFile); 1749958145a97586375966bc133c900ead0e1550f2aYohann Roussel long expectedCrc = 1759958145a97586375966bc133c900ead0e1550f2aYohann Roussel multiDexPreferences.getLong(KEY_DEX_CRC + secondaryNumber, NO_VALUE); 1769958145a97586375966bc133c900ead0e1550f2aYohann Roussel long expectedModTime = 1779958145a97586375966bc133c900ead0e1550f2aYohann Roussel multiDexPreferences.getLong(KEY_DEX_TIME + secondaryNumber, NO_VALUE); 1789958145a97586375966bc133c900ead0e1550f2aYohann Roussel long lastModified = extractedFile.lastModified(); 1799958145a97586375966bc133c900ead0e1550f2aYohann Roussel if ((expectedModTime != lastModified) 1809958145a97586375966bc133c900ead0e1550f2aYohann Roussel || (expectedCrc != extractedFile.crc)) { 1819958145a97586375966bc133c900ead0e1550f2aYohann Roussel throw new IOException("Invalid extracted dex: " + extractedFile + 1829958145a97586375966bc133c900ead0e1550f2aYohann Roussel ", expected modification time: " 1839958145a97586375966bc133c900ead0e1550f2aYohann Roussel + expectedModTime + ", modification time: " 1849958145a97586375966bc133c900ead0e1550f2aYohann Roussel + lastModified + ", expected crc: " 1859958145a97586375966bc133c900ead0e1550f2aYohann Roussel + expectedCrc + ", file crc: " + extractedFile.crc); 186602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 1879958145a97586375966bc133c900ead0e1550f2aYohann Roussel files.add(extractedFile); 188602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } else { 189602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel throw new IOException("Missing extracted secondary dex file '" + 190602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel extractedFile.getPath() + "'"); 191602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 192602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 193602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 194602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel return files; 195602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 196602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 197048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson 198048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson /** 199048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * Compare current archive and crc with values stored in {@link SharedPreferences}. Should be 200048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * called only while owning the lock on {@link #LOCK_FILENAME}. 201048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson */ 202602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static boolean isModified(Context context, File archive, long currentCrc) { 203602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel SharedPreferences prefs = getMultiDexPreferences(context); 204602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive)) 205602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel || (prefs.getLong(KEY_CRC, NO_VALUE) != currentCrc); 206602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 207602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 208602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static long getTimeStamp(File archive) { 209602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel long timeStamp = archive.lastModified(); 210602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel if (timeStamp == NO_VALUE) { 211602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel // never return NO_VALUE 212602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel timeStamp--; 213602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 214602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel return timeStamp; 215602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 216602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 217602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 218602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static long getZipCrc(File archive) throws IOException { 219602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel long computedValue = ZipUtil.getZipCrc(archive); 220602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel if (computedValue == NO_VALUE) { 221602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel // never return NO_VALUE 222602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel computedValue--; 223602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 224602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel return computedValue; 225602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 226602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 2279958145a97586375966bc133c900ead0e1550f2aYohann Roussel private static List<ExtractedDex> performExtractions(File sourceApk, File dexDir) 228602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel throws IOException { 229602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 2307e267a38525afac2a571da186e770a2b86a01846Maurice Chu final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; 231667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 232994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that 233994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // contains a secondary dex file in there is not consistent with the latest apk. Otherwise, 234994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // multi-process race conditions can cause a crash loop where one process deletes the zip 235994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu // while another had created it. 236994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu prepareDexDir(dexDir, extractedFilePrefix); 237667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 2389958145a97586375966bc133c900ead0e1550f2aYohann Roussel List<ExtractedDex> files = new ArrayList<ExtractedDex>(); 2397e267a38525afac2a571da186e770a2b86a01846Maurice Chu 240602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel final ZipFile apk = new ZipFile(sourceApk); 241667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 242667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 243667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int secondaryNumber = 2; 244667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 245667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 246667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu while (dexFile != null) { 247667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; 2489958145a97586375966bc133c900ead0e1550f2aYohann Roussel ExtractedDex extractedFile = new ExtractedDex(dexDir, fileName); 249667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu files.add(extractedFile); 250667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 251602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel Log.i(TAG, "Extraction is needed for file " + extractedFile); 252602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel int numAttempts = 0; 253602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel boolean isExtractionSuccessful = false; 254602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { 255602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel numAttempts++; 256602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 257602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel // Create a zip file (extractedFile) containing only the secondary dex file 258602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel // (dexFile) from the apk. 259602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel extract(apk, dexFile, extractedFile, extractedFilePrefix); 260602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 2619958145a97586375966bc133c900ead0e1550f2aYohann Roussel // Read zip crc of extracted dex 2629958145a97586375966bc133c900ead0e1550f2aYohann Roussel try { 2639958145a97586375966bc133c900ead0e1550f2aYohann Roussel extractedFile.crc = getZipCrc(extractedFile); 2649958145a97586375966bc133c900ead0e1550f2aYohann Roussel isExtractionSuccessful = true; 2659958145a97586375966bc133c900ead0e1550f2aYohann Roussel } catch (IOException e) { 2669958145a97586375966bc133c900ead0e1550f2aYohann Roussel isExtractionSuccessful = false; 2679958145a97586375966bc133c900ead0e1550f2aYohann Roussel Log.w(TAG, "Failed to read crc from " + extractedFile.getAbsolutePath(), e); 2689958145a97586375966bc133c900ead0e1550f2aYohann Roussel } 269602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel 2709958145a97586375966bc133c900ead0e1550f2aYohann Roussel // Log size and crc of the extracted zip file 2719958145a97586375966bc133c900ead0e1550f2aYohann Roussel Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "succeeded" : "failed") + 272602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel " - length " + extractedFile.getAbsolutePath() + ": " + 2739958145a97586375966bc133c900ead0e1550f2aYohann Roussel extractedFile.length() + " - crc: " + extractedFile.crc); 274602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel if (!isExtractionSuccessful) { 275602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel // Delete the extracted file 276602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel extractedFile.delete(); 277602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel if (extractedFile.exists()) { 278602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel Log.w(TAG, "Failed to delete corrupted secondary dex '" + 279602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel extractedFile.getPath() + "'"); 280f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 281f6d1f23926672c8dd61da515f8d1bcb37ef4292dMaurice Chu } 282602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel } 283602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel if (!isExtractionSuccessful) { 284602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel throw new IOException("Could not create zip file " + 285602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel extractedFile.getAbsolutePath() + " for secondary dex (" + 286602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel secondaryNumber + ")"); 287667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 288667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu secondaryNumber++; 289667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 290667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 291667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 292667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 293667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu apk.close(); 294667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 295667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 296667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 297667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 298667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 299667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return files; 300667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 301667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 302048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson /** 303048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * Save {@link SharedPreferences}. Should be called only while owning the lock on 304048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * {@link #LOCK_FILENAME}. 305048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson */ 306602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel private static void putStoredApkInfo(Context context, long timeStamp, long crc, 3079958145a97586375966bc133c900ead0e1550f2aYohann Roussel List<ExtractedDex> extractedDexes) { 308994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu SharedPreferences prefs = getMultiDexPreferences(context); 3097e267a38525afac2a571da186e770a2b86a01846Maurice Chu SharedPreferences.Editor edit = prefs.edit(); 310602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel edit.putLong(KEY_TIME_STAMP, timeStamp); 311602c6ca8cae4718ba8ff9f65e53305d002479359Yohann Roussel edit.putLong(KEY_CRC, crc); 3129958145a97586375966bc133c900ead0e1550f2aYohann Roussel edit.putInt(KEY_DEX_NUMBER, extractedDexes.size() + 1); 3139958145a97586375966bc133c900ead0e1550f2aYohann Roussel 3149958145a97586375966bc133c900ead0e1550f2aYohann Roussel int extractedDexId = 2; 3159958145a97586375966bc133c900ead0e1550f2aYohann Roussel for (ExtractedDex dex : extractedDexes) { 3169958145a97586375966bc133c900ead0e1550f2aYohann Roussel edit.putLong(KEY_DEX_CRC + extractedDexId, dex.crc); 3179958145a97586375966bc133c900ead0e1550f2aYohann Roussel edit.putLong(KEY_DEX_TIME + extractedDexId, dex.lastModified()); 3189958145a97586375966bc133c900ead0e1550f2aYohann Roussel extractedDexId++; 3199958145a97586375966bc133c900ead0e1550f2aYohann Roussel } 320048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson /* Use commit() and not apply() as advised by the doc because we need synchronous writing of 321048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * the editor content and apply is doing an "asynchronous commit to disk". 322048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson */ 323048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson edit.commit(); 3247e267a38525afac2a571da186e770a2b86a01846Maurice Chu } 3257e267a38525afac2a571da186e770a2b86a01846Maurice Chu 326048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson /** 327048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * Get the MuliDex {@link SharedPreferences} for the current application. Should be called only 328048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * while owning the lock on {@link #LOCK_FILENAME}. 329048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson */ 330994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu private static SharedPreferences getMultiDexPreferences(Context context) { 331994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu return context.getSharedPreferences(PREFS_FILE, 332e99daea7a3aec5ffac13b4283685e8d2a5994ad9Yohann Roussel Build.VERSION.SDK_INT < 11 /* Build.VERSION_CODES.HONEYCOMB */ 333994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu ? Context.MODE_PRIVATE 334e99daea7a3aec5ffac13b4283685e8d2a5994ad9Yohann Roussel : Context.MODE_PRIVATE | 0x0004 /* Context.MODE_MULTI_PROCESS */); 335994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu } 336994fa84b3af07700fd5dcd477c02a6bd824dde45Maurice Chu 3377e267a38525afac2a571da186e770a2b86a01846Maurice Chu /** 338048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson * This removes old files. 339cc63eda4e6defe6b0dd5dd3c8fa608cf6ff26011Maurice Chu */ 340606af94785cb96d418d87fe5a90bb2e09ccfa97fYohann Roussel private static void prepareDexDir(File dexDir, final String extractedFilePrefix) { 341d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel FileFilter filter = new FileFilter() { 342d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel 343667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu @Override 344d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel public boolean accept(File pathname) { 345048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson String name = pathname.getName(); 346048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson return !(name.startsWith(extractedFilePrefix) 347048fbf7ccf53782a265c277df38c273d43e5450eAndrew Johnson || name.equals(LOCK_FILENAME)); 348667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 349667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu }; 350667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File[] files = dexDir.listFiles(filter); 351667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (files == null) { 352667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); 353667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 354667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 355667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (File oldFile : files) { 356d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " + 3577e267a38525afac2a571da186e770a2b86a01846Maurice Chu oldFile.length()); 358667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!oldFile.delete()) { 359667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); 36088117c37e2c3f8601f295b74a3e804877afb78eeYohann Roussel } else { 361d79604bd38c101b54e41745f85ddc2e04d978af2Yohann Roussel Log.i(TAG, "Deleted old file " + oldFile.getPath()); 362667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 363667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 364667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 365667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 36652eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, 3677e267a38525afac2a571da186e770a2b86a01846Maurice Chu String extractedFilePrefix) throws IOException, FileNotFoundException { 368667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 369667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu InputStream in = apk.getInputStream(dexFile); 370667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipOutputStream out = null; 3714b56ee2bb0401ccf35bdecaaf86b2212e98db919Jon Noack // Temp files must not start with extractedFilePrefix to get cleaned up in prepareDexDir() 3724b56ee2bb0401ccf35bdecaaf86b2212e98db919Jon Noack File tmp = File.createTempFile("tmp-" + extractedFilePrefix, EXTRACTED_SUFFIX, 373667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getParentFile()); 374667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Extracting " + tmp.getPath()); 375667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 376a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp))); 377667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 378667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry classesDex = new ZipEntry("classes.dex"); 379edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel // keep zip entry time since it is the criteria used by Dalvik 380edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel classesDex.setTime(dexFile.getTime()); 381667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out.putNextEntry(classesDex); 382667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 383667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu byte[] buffer = new byte[BUFFER_SIZE]; 384667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int length = in.read(buffer); 3851f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu while (length != -1) { 38652eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel out.write(buffer, 0, length); 387667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu length = in.read(buffer); 388667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 389a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu out.closeEntry(); 390667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 391a0c1a85f60a44ef1c0592821270c20e988c64370Maurice Chu out.close(); 392667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 393f5832474ebd6ec48f0f37bc1fd9e2576068e4ef2Yohann Roussel if (!tmp.setReadOnly()) { 394f5832474ebd6ec48f0f37bc1fd9e2576068e4ef2Yohann Roussel throw new IOException("Failed to mark readonly \"" + tmp.getAbsolutePath() + 395f5832474ebd6ec48f0f37bc1fd9e2576068e4ef2Yohann Roussel "\" (tmp of \"" + extractTo.getAbsolutePath() + "\")"); 396f5832474ebd6ec48f0f37bc1fd9e2576068e4ef2Yohann Roussel } 397667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Renaming to " + extractTo.getPath()); 398a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu if (!tmp.renameTo(extractTo)) { 399a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + 400a159fd5dbd60171c9bb602f316ad8272419afe40Maurice Chu "\" to \"" + extractTo.getAbsolutePath() + "\""); 401667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 402667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 403667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(in); 404667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu tmp.delete(); // return status ignored 405667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 406667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 407667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 408667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 409667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Closes the given {@code Closeable}. Suppresses any IO exceptions. 410667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 411667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void closeQuietly(Closeable closeable) { 412667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 413667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeable.close(); 414667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 415667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 416667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 417667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 418667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu} 419