blob: 49f23ebe2a200e57c72b91d5527b18365bc8f072 [file] [log] [blame]
// 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) {
}
}
}