1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 5 package multidex004.fakelibrary; 6 7 import multidex004.fakeframeworks.Context; 8 9 import java.io.Closeable; 10 import java.io.File; 11 import java.io.FileFilter; 12 import java.io.IOException; 13 import java.util.List; 14 15 /** 16 * Exposes application secondary dex files as files in the application data 17 * directory. 18 */ 19 final class MultiDexExtractor { 20 21 private static final String TAG = MultiDex.TAG; 22 23 /** 24 * We look for additional dex files named {@code classes2.dex}, 25 * {@code classes3.dex}, etc. 26 */ 27 private static final String DEX_PREFIX = "classes"; 28 private static final String DEX_SUFFIX = ".dex"; 29 30 private static final String EXTRACTED_NAME_EXT = ".classes"; 31 private static final String EXTRACTED_SUFFIX = ".zip"; 32 private static final int MAX_EXTRACT_ATTEMPTS = 3; 33 34 private static final String PREFS_FILE = "multidex.version"; 35 private static final String KEY_TIME_STAMP = "timestamp"; 36 private static final String KEY_CRC = "crc"; 37 private static final String KEY_DEX_NUMBER = "dex.number"; 38 39 /** 40 * Size of reading buffers. 41 */ 42 private static final int BUFFER_SIZE = 0x4000; 43 /* Keep value away from 0 because it is a too probable time stamp value */ 44 private static final long NO_VALUE = -1L; 45 46 47 private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir) 48 throws IOException { 49 50 final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; 51 int totalDexNumber = 1; 52 final List<File> files = null; 53 54 for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { 55 String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; 56 File extractedFile = new File(dexDir, fileName); 57 if (extractedFile.isFile()) { 58 files.add(extractedFile); 59 } else { 60 throw new IOException("Missing extracted secondary dex file '" + 61 extractedFile.getPath() + "'"); 62 } 63 } 64 65 return files; 66 } 67 68 private static long getTimeStamp(File archive) { 69 long timeStamp = archive.lastModified(); 70 if (timeStamp == NO_VALUE) { 71 // never return NO_VALUE 72 timeStamp--; 73 } 74 return timeStamp; 75 } 76 77 78 private static long getZipCrc(File archive) throws IOException { 79 long computedValue = ZipUtil.getZipCrc(archive); 80 if (computedValue == NO_VALUE) { 81 // never return NO_VALUE 82 computedValue--; 83 } 84 return computedValue; 85 } 86 87 private static List<File> performExtractions(File sourceApk, File dexDir) 88 throws IOException { 89 90 final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; 91 92 // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that 93 // contains a secondary dex file in there is not consistent with the latest apk. Otherwise, 94 // multi-process race conditions can cause a crash loop where one process deletes the zip 95 // while another had created it. 96 prepareDexDir(dexDir, extractedFilePrefix); 97 98 List<File> files = null; 99 100 return files; 101 } 102 103 /** 104 * This removes any files that do not have the correct prefix. 105 */ 106 private static void prepareDexDir(File dexDir, final String extractedFilePrefix) 107 throws IOException { 108 dexDir.mkdir(); 109 if (!dexDir.isDirectory()) { 110 throw new IOException("Failed to create dex directory " + dexDir.getPath()); 111 } 112 113 // Clean possible old files 114 FileFilter filter = new FileFilter() { 115 116 @Override 117 public boolean accept(File pathname) { 118 return !pathname.getName().startsWith(extractedFilePrefix); 119 } 120 }; 121 File[] files = dexDir.listFiles(filter); 122 if (files == null) { 123 return; 124 } 125 for (File oldFile : files) { 126 if (!oldFile.delete()) { 127 } else { 128 } 129 } 130 } 131 132 /** 133 * Closes the given {@code Closeable}. Suppresses any IO exceptions. 134 */ 135 private static void closeQuietly(Closeable closeable) { 136 try { 137 closeable.close(); 138 } catch (IOException e) { 139 } 140 } 141 142 } 143