/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.jack.multidex.fakelibrary; import com.android.jack.multidex.fakeframeworks.Context; import java.io.Closeable; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.List; /** * 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 loadExistingExtractions(Context context, File sourceApk, File dexDir) throws IOException { final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; int totalDexNumber = 1; final List 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 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 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) { } } }