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