| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| package multidex003.fakelibrary; | |
| import java.io.Closeable; | |
| import java.io.File; | |
| import java.io.FileFilter; | |
| import java.io.IOException; | |
| import java.util.List; | |
| import multidexfakeframeworks.Context; | |
| /** | |
| * Exposes application secondary dex files as files in the application data | |
| * directory. | |
| */ | |
| final class MultiDexExtractor { | |
| private static final String TAG = MultiDex.TAG; | |
| /** | |
| * We look for additional dex files named {@code classes2.dex}, | |
| * {@code classes3.dex}, etc. | |
| */ | |
| private static final String DEX_PREFIX = "classes"; | |
| private static final String DEX_SUFFIX = ".dex"; | |
| private static final String EXTRACTED_NAME_EXT = ".classes"; | |
| private static final String EXTRACTED_SUFFIX = ".zip"; | |
| private static final int MAX_EXTRACT_ATTEMPTS = 3; | |
| private static final String PREFS_FILE = "multidex.version"; | |
| private static final String KEY_TIME_STAMP = "timestamp"; | |
| private static final String KEY_CRC = "crc"; | |
| private static final String KEY_DEX_NUMBER = "dex.number"; | |
| /** | |
| * Size of reading buffers. | |
| */ | |
| private static final int BUFFER_SIZE = 0x4000; | |
| /* Keep value away from 0 because it is a too probable time stamp value */ | |
| private static final long NO_VALUE = -1L; | |
| private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir) | |
| throws IOException { | |
| final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; | |
| int totalDexNumber = 1; | |
| final List<File> files = null; | |
| for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { | |
| String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; | |
| File extractedFile = new File(dexDir, fileName); | |
| if (extractedFile.isFile()) { | |
| files.add(extractedFile); | |
| } else { | |
| throw new IOException("Missing extracted secondary dex file '" + | |
| extractedFile.getPath() + "'"); | |
| } | |
| } | |
| return files; | |
| } | |
| private static long getTimeStamp(File archive) { | |
| long timeStamp = archive.lastModified(); | |
| if (timeStamp == NO_VALUE) { | |
| // never return NO_VALUE | |
| timeStamp--; | |
| } | |
| return timeStamp; | |
| } | |
| private static long getZipCrc(File archive) throws IOException { | |
| long computedValue = ZipUtil.getZipCrc(archive); | |
| if (computedValue == NO_VALUE) { | |
| // never return NO_VALUE | |
| computedValue--; | |
| } | |
| return computedValue; | |
| } | |
| private static List<File> performExtractions(File sourceApk, File dexDir) | |
| throws IOException { | |
| final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; | |
| // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that | |
| // contains a secondary dex file in there is not consistent with the latest apk. Otherwise, | |
| // multi-process race conditions can cause a crash loop where one process deletes the zip | |
| // while another had created it. | |
| prepareDexDir(dexDir, extractedFilePrefix); | |
| List<File> files = null; | |
| return files; | |
| } | |
| /** | |
| * This removes any files that do not have the correct prefix. | |
| */ | |
| private static void prepareDexDir(File dexDir, final String extractedFilePrefix) | |
| throws IOException { | |
| dexDir.mkdir(); | |
| if (!dexDir.isDirectory()) { | |
| throw new IOException("Failed to create dex directory " + dexDir.getPath()); | |
| } | |
| // Clean possible old files | |
| FileFilter filter = new FileFilter() { | |
| @Override | |
| public boolean accept(File pathname) { | |
| return !pathname.getName().startsWith(extractedFilePrefix); | |
| } | |
| }; | |
| File[] files = dexDir.listFiles(filter); | |
| if (files == null) { | |
| return; | |
| } | |
| for (File oldFile : files) { | |
| if (!oldFile.delete()) { | |
| } else { | |
| } | |
| } | |
| } | |
| /** | |
| * Closes the given {@code Closeable}. Suppresses any IO exceptions. | |
| */ | |
| private static void closeQuietly(Closeable closeable) { | |
| try { | |
| closeable.close(); | |
| } catch (IOException e) { | |
| } | |
| } | |
| } |