merge in pi-release history after reset to master
diff --git a/library/src/android/support/multidex/MultiDex.java b/library/src/android/support/multidex/MultiDex.java
index 646e023..ab7f668 100644
--- a/library/src/android/support/multidex/MultiDex.java
+++ b/library/src/android/support/multidex/MultiDex.java
@@ -114,8 +114,7 @@
                     new File(applicationInfo.sourceDir),
                     new File(applicationInfo.dataDir),
                     CODE_CACHE_SECONDARY_FOLDER_NAME,
-                    NO_KEY_PREFIX,
-                    true);
+                    NO_KEY_PREFIX);
 
         } catch (Exception e) {
             Log.e(TAG, "MultiDex installation failure", e);
@@ -172,15 +171,13 @@
                     new File(instrumentationInfo.sourceDir),
                     dataDir,
                     instrumentationPrefix + CODE_CACHE_SECONDARY_FOLDER_NAME,
-                    instrumentationPrefix,
-                    false);
+                    instrumentationPrefix);
 
             doInstallation(targetContext,
                     new File(applicationInfo.sourceDir),
                     dataDir,
                     CODE_CACHE_SECONDARY_FOLDER_NAME,
-                    NO_KEY_PREFIX,
-                    false);
+                    NO_KEY_PREFIX);
         } catch (Exception e) {
             Log.e(TAG, "MultiDex installation failure", e);
             throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ").");
@@ -195,12 +192,9 @@
      * @param dataDir data directory to use for code cache simulation.
      * @param secondaryFolderName name of the folder for storing extractions.
      * @param prefsKeyPrefix prefix of all stored preference keys.
-     * @param reinstallOnPatchRecoverableException if set to true, will attempt a clean extraction
-     * if a possibly recoverable exception occurs during classloader patching.
      */
     private static void doInstallation(Context mainContext, File sourceApk, File dataDir,
-            String secondaryFolderName, String prefsKeyPrefix,
-            boolean reinstallOnPatchRecoverableException) throws IOException,
+            String secondaryFolderName, String prefsKeyPrefix) throws IOException,
                 IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
                 InvocationTargetException, NoSuchMethodException {
         synchronized (installedApk) {
@@ -251,38 +245,9 @@
             }
 
             File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
-            // MultiDexExtractor is taking the file lock and keeping it until it is closed.
-            // Keep it open during installSecondaryDexes and through forced extraction to ensure no
-            // extraction or optimizing dexopt is running in parallel.
-            MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
-            IOException closeException = null;
-            try {
-                List<? extends File> files =
-                        extractor.load(mainContext, prefsKeyPrefix, false);
-                try {
-                    installSecondaryDexes(loader, dexDir, files);
-                // Some IOException causes may be fixed by a clean extraction.
-                } catch (IOException e) {
-                    if (!reinstallOnPatchRecoverableException) {
-                        throw e;
-                    }
-                    Log.w(TAG, "Failed to install extracted secondary dex files, retrying with "
-                            + "forced extraction", e);
-                    files = extractor.load(mainContext, prefsKeyPrefix, true);
-                    installSecondaryDexes(loader, dexDir, files);
-                }
-            } finally {
-                try {
-                    extractor.close();
-                } catch (IOException e) {
-                    // Delay throw of close exception to ensure we don't override some exception
-                    // thrown during the try block.
-                    closeException = e;
-                }
-            }
-            if (closeException != null) {
-                throw closeException;
-            }
+            List<? extends File> files =
+                    MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false);
+            installSecondaryDexes(loader, dexDir, files);
         }
     }
 
@@ -499,8 +464,7 @@
                 List<? extends File> additionalClassPathEntries,
                 File optimizedDirectory)
                         throws IllegalArgumentException, IllegalAccessException,
-                        NoSuchFieldException, InvocationTargetException, NoSuchMethodException,
-                        IOException {
+                        NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
             /* The patched class loader is expected to be a descendant of
              * dalvik.system.BaseDexClassLoader. We modify its
              * dalvik.system.DexPathList pathList field to append additional DEX
@@ -536,10 +500,6 @@
                 }
 
                 suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
-
-                IOException exception = new IOException("I/O exception during makeDexElement");
-                exception.initCause(suppressedExceptions.get(0));
-                throw exception;
             }
         }
 
diff --git a/library/src/android/support/multidex/MultiDexExtractor.java b/library/src/android/support/multidex/MultiDexExtractor.java
index ed3c125..39b6bf7 100644
--- a/library/src/android/support/multidex/MultiDexExtractor.java
+++ b/library/src/android/support/multidex/MultiDexExtractor.java
@@ -40,10 +40,8 @@
 /**
  * Exposes application secondary dex files as files in the application data
  * directory.
- * {@link MultiDexExtractor} is taking the file lock in the dex dir on creation and release it
- * during close.
  */
-final class MultiDexExtractor implements Closeable {
+final class MultiDexExtractor {
 
     /**
      * Zip file containing one secondary dex file.
@@ -84,35 +82,6 @@
     private static final long NO_VALUE = -1L;
 
     private static final String LOCK_FILENAME = "MultiDex.lock";
-    private final File sourceApk;
-    private final long sourceCrc;
-    private final File dexDir;
-    private final RandomAccessFile lockRaf;
-    private final FileChannel lockChannel;
-    private final FileLock cacheLock;
-
-    MultiDexExtractor(File sourceApk, File dexDir) throws IOException {
-        Log.i(TAG, "MultiDexExtractor(" + sourceApk.getPath() + ", " + dexDir.getPath() + ")");
-        this.sourceApk = sourceApk;
-        this.dexDir = dexDir;
-        sourceCrc = getZipCrc(sourceApk);
-        File lockFile = new File(dexDir, LOCK_FILENAME);
-        lockRaf = new RandomAccessFile(lockFile, "rw");
-        try {
-            lockChannel = lockRaf.getChannel();
-            try {
-                Log.i(TAG, "Blocking on lock " + lockFile.getPath());
-                cacheLock = lockChannel.lock();
-            } catch (IOException | RuntimeException | Error e) {
-                closeQuietly(lockChannel);
-                throw e;
-            }
-            Log.i(TAG, lockFile.getPath() + " locked");
-        } catch (IOException | RuntimeException | Error e) {
-            closeQuietly(lockRaf);
-            throw e;
-        }
-    }
 
     /**
      * Extracts application secondary dexes into files in the application data
@@ -123,54 +92,74 @@
      * @throws IOException if encounters a problem while reading or writing
      *         secondary dex files
      */
-    List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload)
-            throws IOException {
+    static List<? extends File> load(Context context, File sourceApk, File dexDir,
+            String prefsKeyPrefix,
+            boolean forceReload) throws IOException {
         Log.i(TAG, "MultiDexExtractor.load(" + sourceApk.getPath() + ", " + forceReload + ", " +
                 prefsKeyPrefix + ")");
 
-        if (!cacheLock.isValid()) {
-            throw new IllegalStateException("MultiDexExtractor was closed");
-        }
+        long currentCrc = getZipCrc(sourceApk);
 
+        // Validity check and extraction must be done only while the lock file has been taken.
+        File lockFile = new File(dexDir, LOCK_FILENAME);
+        RandomAccessFile lockRaf = new RandomAccessFile(lockFile, "rw");
+        FileChannel lockChannel = null;
+        FileLock cacheLock = null;
         List<ExtractedDex> files;
-        if (!forceReload && !isModified(context, sourceApk, sourceCrc, prefsKeyPrefix)) {
-            try {
-                files = loadExistingExtractions(context, prefsKeyPrefix);
-            } catch (IOException ioe) {
-                Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
-                        + " falling back to fresh extraction", ioe);
-                files = performExtractions();
-                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc,
-                        files);
-            }
-        } else {
-            if (forceReload) {
-                Log.i(TAG, "Forced extraction must be performed.");
+        IOException releaseLockException = null;
+        try {
+            lockChannel = lockRaf.getChannel();
+            Log.i(TAG, "Blocking on lock " + lockFile.getPath());
+            cacheLock = lockChannel.lock();
+            Log.i(TAG, lockFile.getPath() + " locked");
+
+            if (!forceReload && !isModified(context, sourceApk, currentCrc, prefsKeyPrefix)) {
+                try {
+                    files = loadExistingExtractions(context, sourceApk, dexDir, prefsKeyPrefix);
+                } catch (IOException ioe) {
+                    Log.w(TAG, "Failed to reload existing extracted secondary dex files,"
+                            + " falling back to fresh extraction", ioe);
+                    files = performExtractions(sourceApk, dexDir);
+                    putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc,
+                            files);
+                }
             } else {
                 Log.i(TAG, "Detected that extraction must be performed.");
+                files = performExtractions(sourceApk, dexDir);
+                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc,
+                        files);
             }
-            files = performExtractions();
-            putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc,
-                    files);
+        } finally {
+            if (cacheLock != null) {
+                try {
+                    cacheLock.release();
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to release lock on " + lockFile.getPath());
+                    // Exception while releasing the lock is bad, we want to report it, but not at
+                    // the price of overriding any already pending exception.
+                    releaseLockException = e;
+                }
+            }
+            if (lockChannel != null) {
+                closeQuietly(lockChannel);
+            }
+            closeQuietly(lockRaf);
+        }
+
+        if (releaseLockException != null) {
+            throw releaseLockException;
         }
 
         Log.i(TAG, "load found " + files.size() + " secondary dex files");
         return files;
     }
 
-    @Override
-    public void close() throws IOException {
-        cacheLock.release();
-        lockChannel.close();
-        lockRaf.close();
-    }
-
     /**
      * Load previously extracted secondary dex files. Should be called only while owning the lock on
      * {@link #LOCK_FILENAME}.
      */
-    private List<ExtractedDex> loadExistingExtractions(
-            Context context,
+    private static List<ExtractedDex> loadExistingExtractions(
+            Context context, File sourceApk, File dexDir,
             String prefsKeyPrefix)
             throws IOException {
         Log.i(TAG, "loading existing secondary dex files");
@@ -239,14 +228,16 @@
         return computedValue;
     }
 
-    private List<ExtractedDex> performExtractions() throws IOException {
+    private static List<ExtractedDex> performExtractions(File sourceApk, File dexDir)
+            throws IOException {
 
         final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
 
-        // It is safe to fully clear the dex dir because we own the file lock so no other process is
-        // extracting or running optimizing dexopt. It may cause crash of already running
-        // applications if for whatever reason we end up extracting again over a valid extraction.
-        clearDexDir();
+        // 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<ExtractedDex> files = new ArrayList<ExtractedDex>();
 
@@ -281,9 +272,9 @@
                     }
 
                     // Log size and crc of the extracted zip file
-                    Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "succeeded" : "failed")
-                            + " '" + extractedFile.getAbsolutePath() + "': length "
-                            + extractedFile.length() + " - crc: " + extractedFile.crc);
+                    Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "succeeded" : "failed") +
+                            " - length " + extractedFile.getAbsolutePath() + ": " +
+                            extractedFile.length() + " - crc: " + extractedFile.crc);
                     if (!isExtractionSuccessful) {
                         // Delete the extracted file
                         extractedFile.delete();
@@ -348,15 +339,19 @@
     }
 
     /**
-     * Clear the dex dir from all files but the lock.
+     * This removes old files.
      */
-    private void clearDexDir() {
-        File[] files = dexDir.listFiles(new FileFilter() {
+    private static void prepareDexDir(File dexDir, final String extractedFilePrefix) {
+        FileFilter filter = new FileFilter() {
+
             @Override
             public boolean accept(File pathname) {
-                return !pathname.getName().equals(LOCK_FILENAME);
+                String name = pathname.getName();
+                return !(name.startsWith(extractedFilePrefix)
+                        || name.equals(LOCK_FILENAME));
             }
-        });
+        };
+        File[] files = dexDir.listFiles(filter);
         if (files == null) {
             Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
             return;