Fix lookup order when opening directly from APK

The order should be as follows:
1. Uncompressed native library dir (if any)
2. Directly from apk (<apk>!/lib/<abi>)
3. vendor/lib:/system/lib

Bug: http://b/21647354
Bug: http://b/21667767
Bug: http://b/21726698
Bug: http://b/8076853
Change-Id: I62cd76b7e4ae927d865d7d0ee81ceb91caa54e99
diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
index 4124ffa..5ef00bf 100644
--- a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
+++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@@ -130,24 +130,8 @@
             }
             result.append(directory);
         }
-        return result.toString();
-    }
 
-    /**
-     * Returns the list of jar/apk files containing classes and
-     * resources, delimited by {@code File.pathSeparator}.
-     *
-     * @hide
-     */
-    public String getDexPath() {
-        StringBuilder builder = new StringBuilder();
-        for (File file : pathList.getDexFiles()) {
-            if (builder.length() > 0) {
-                builder.append(':');
-            }
-            builder.append(file);
-        }
-        return builder.toString();
+        return result.toString();
     }
 
     @Override public String toString() {
diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java
index 506a156..f0b1ffd 100644
--- a/dalvik/src/main/java/dalvik/system/DexPathList.java
+++ b/dalvik/src/main/java/dalvik/system/DexPathList.java
@@ -27,6 +27,7 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.zip.ZipEntry;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
 import libcore.io.ClassPathURLStreamHandler;
@@ -48,13 +49,11 @@
  */
 /*package*/ final class DexPathList {
     private static final String DEX_SUFFIX = ".dex";
+    private static final String zipSeparator = "!/";
 
     /** class definition context */
     private final ClassLoader definingContext;
 
-    /** List of dexfiles. */
-    private final List<File> dexFiles;
-
     /**
      * List of dex/resource (class path) elements.
      * Should be called pathElements, but the Facebook app uses reflection
@@ -62,9 +61,15 @@
      */
     private final Element[] dexElements;
 
-    /** List of native library directories. */
+    /** List of native library path elements. */
+    private final Element[] nativeLibraryPathElements;
+
+    /** List of application native library directories. */
     private final List<File> nativeLibraryDirectories;
 
+    /** List of system native library directories. */
+    private final List<File> systemNativeLibraryDirectories;
+
     /**
      * Exceptions thrown during creation of the dexElements list.
      */
@@ -85,6 +90,7 @@
      */
     public DexPathList(ClassLoader definingContext, String dexPath,
             String libraryPath, File optimizedDirectory) {
+
         if (definingContext == null) {
             throw new NullPointerException("definingContext == null");
         }
@@ -109,23 +115,46 @@
         }
 
         this.definingContext = definingContext;
+
         ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
         // save dexPath for BaseDexClassLoader
-        this.dexFiles = splitDexPath(dexPath);
-        this.dexElements = makeDexElements(dexFiles, optimizedDirectory,
+        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                            suppressedExceptions);
+
+        // Native libraries may exist in both the system and
+        // application library paths, and we use this search order:
+        //
+        //   1. This class loader's library path for application libraries (libraryPath):
+        //   1.1. Native library directories
+        //   1.2. Path to libraries in apk-files
+        //   2. The VM's library path from the system property for system libraries
+        //      also known as java.library.path
+        //
+        // This order was reversed prior to Gingerbread; see http://b/2933456.
+        this.nativeLibraryDirectories = splitPaths(libraryPath, false);
+        this.systemNativeLibraryDirectories =
+                splitPaths(System.getProperty("java.library.path"), true);
+        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
+        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
+
+        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
+                                                          suppressedExceptions);
+
         if (suppressedExceptions.size() > 0) {
             this.dexElementsSuppressedExceptions =
                 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
         } else {
             dexElementsSuppressedExceptions = null;
         }
-        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
     }
 
     @Override public String toString() {
+        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
+        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
+
         File[] nativeLibraryDirectoriesArray =
-                nativeLibraryDirectories.toArray(new File[nativeLibraryDirectories.size()]);
+                allNativeLibraryDirectories.toArray(
+                    new File[allNativeLibraryDirectories.size()]);
 
         return "DexPathList[" + Arrays.toString(dexElements) +
             ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectoriesArray) + "]";
@@ -139,38 +168,12 @@
     }
 
     /**
-     * For BaseDexClassLoader.getDexPath.
-     */
-    public List<File> getDexFiles() {
-        return dexFiles;
-    }
-
-    /**
      * Splits the given dex path string into elements using the path
      * separator, pruning out any elements that do not refer to existing
-     * and readable files. (That is, directories are not included in the
-     * result.)
+     * and readable files.
      */
     private static List<File> splitDexPath(String path) {
-        return splitPaths(path, null, false);
-    }
-
-    /**
-     * Splits the given library directory path string into elements
-     * using the path separator ({@code File.pathSeparator}, which
-     * defaults to {@code ":"} on Android, appending on the elements
-     * from the system library path, and pruning out any elements that
-     * do not refer to existing and readable directories.
-     */
-    private static List<File> splitLibraryPath(String path) {
-        // Native libraries may exist in both the system and
-        // application library paths, and we use this search order:
-        //
-        //   1. this class loader's library path for application libraries
-        //   2. the VM's library path from the system property for system libraries
-        //
-        // This order was reversed prior to Gingerbread; see http://b/2933456.
-        return splitPaths(path, System.getProperty("java.library.path"), true);
+        return splitPaths(path, false);
     }
 
     /**
@@ -182,32 +185,26 @@
      * are empty or {@code null}, or all elements get pruned out, then
      * this returns a zero-element list.
      */
-    private static List<File> splitPaths(String path1, String path2, boolean wantDirectories) {
-        List<File> result = new ArrayList<File>();
+    private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
+        List<File> result = new ArrayList<>();
 
-        splitAndAdd(path1, wantDirectories, result);
-        splitAndAdd(path2, wantDirectories, result);
-        return result;
-    }
-
-    /**
-     * Helper for {@link #splitPaths}, which does the actual splitting
-     * and filtering and adding to a result.
-     */
-    private static void splitAndAdd(String searchPath, boolean directoriesOnly,
-            List<File> resultList) {
-        if (searchPath == null) {
-            return;
-        }
-        for (String path : searchPath.split(":")) {
-            try {
-                StructStat sb = Libcore.os.stat(path);
-                if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
-                    resultList.add(new File(path));
+        if (searchPath != null) {
+            for (String path : searchPath.split(File.pathSeparator)) {
+                if (directoriesOnly) {
+                    try {
+                        StructStat sb = Libcore.os.stat(path);
+                        if (!S_ISDIR(sb.st_mode)) {
+                            continue;
+                        }
+                    } catch (ErrnoException ignored) {
+                        continue;
+                    }
                 }
-            } catch (ErrnoException ignored) {
+                result.add(new File(path));
             }
         }
+
+        return result;
     }
 
     /**
@@ -216,22 +213,42 @@
      */
     private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
                                              List<IOException> suppressedExceptions) {
-        ArrayList<Element> elements = new ArrayList<Element>();
+        return makeElements(files, optimizedDirectory, suppressedExceptions, false);
+    }
+
+    /**
+     * Makes an array of directory/zip path elements, one per element of the given array.
+     */
+    private static Element[] makePathElements(List<File> files,
+                                              List<IOException> suppressedExceptions) {
+        return makeElements(files, null, suppressedExceptions, true);
+    }
+
+    private static Element[] makeElements(List<File> files, File optimizedDirectory,
+                                          List<IOException> suppressedExceptions,
+                                          boolean ignoreDexFiles) {
+        List<Element> elements = new ArrayList<>();
         /*
          * Open all files and load the (direct or contained) dex files
          * up front.
          */
         for (File file : files) {
             File zip = null;
+            File dir = new File("");
             DexFile dex = null;
+            String path = file.getPath();
             String name = file.getName();
 
-            if (file.isDirectory()) {
-                // We support directories for looking up resources.
-                // This is only useful for running libcore tests.
+            if (path.contains(zipSeparator)) {
+                String split[] = path.split(zipSeparator, 2);
+                zip = new File(split[0]);
+                dir = new File(split[1]);
+            } else if (file.isDirectory()) {
+                // We support directories for looking up resources and native libraries.
+                // Looking up resources in directories is useful for running libcore tests.
                 elements.add(new Element(file, true, null, null));
-            } else if (file.isFile()){
-                if (name.endsWith(DEX_SUFFIX)) {
+            } else if (file.isFile()) {
+                if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
                     // Raw dex file (not inside a zip/jar).
                     try {
                         dex = loadDexFile(file, optimizedDirectory);
@@ -241,17 +258,19 @@
                 } else {
                     zip = file;
 
-                    try {
-                        dex = loadDexFile(file, optimizedDirectory);
-                    } catch (IOException suppressed) {
-                        /*
-                         * IOException might get thrown "legitimately" by the DexFile constructor if
-                         * the zip file turns out to be resource-only (that is, no classes.dex file
-                         * in it).
-                         * Let dex == null and hang on to the exception to add to the tea-leaves for
-                         * when findClass returns null.
-                         */
-                        suppressedExceptions.add(suppressed);
+                    if (!ignoreDexFiles) {
+                        try {
+                            dex = loadDexFile(file, optimizedDirectory);
+                        } catch (IOException suppressed) {
+                            /*
+                             * IOException might get thrown "legitimately" by the DexFile constructor if
+                             * the zip file turns out to be resource-only (that is, no classes.dex file
+                             * in it).
+                             * Let dex == null and hang on to the exception to add to the tea-leaves for
+                             * when findClass returns null.
+                             */
+                            suppressedExceptions.add(suppressed);
+                        }
                     }
                 }
             } else {
@@ -259,7 +278,7 @@
             }
 
             if ((zip != null) || (dex != null)) {
-                elements.add(new Element(file, false, zip, dex));
+                elements.add(new Element(dir, false, zip, dex));
             }
         }
 
@@ -392,20 +411,23 @@
      */
     public String findLibrary(String libraryName) {
         String fileName = System.mapLibraryName(libraryName);
-        for (File directory : nativeLibraryDirectories) {
-            String path = new File(directory, fileName).getPath();
-            if (IoUtils.canOpenReadOnly(path)) {
+
+        for (Element element : nativeLibraryPathElements) {
+            String path = element.findNativeLibrary(fileName);
+
+            if (path != null) {
                 return path;
             }
         }
+
         return null;
     }
 
     /**
-     * Element of the dex/resource file path
+     * Element of the dex/resource/native library path
      */
     /*package*/ static class Element {
-        private final File file;
+        private final File dir;
         private final boolean isDirectory;
         private final File zip;
         private final DexFile dexFile;
@@ -413,8 +435,8 @@
         private ClassPathURLStreamHandler urlHandler;
         private boolean initialized;
 
-        public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
-            this.file = file;
+        public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
+            this.dir = dir;
             this.isDirectory = isDirectory;
             this.zip = zip;
             this.dexFile = dexFile;
@@ -422,9 +444,10 @@
 
         @Override public String toString() {
             if (isDirectory) {
-                return "directory \"" + file + "\"";
+                return "directory \"" + dir + "\"";
             } else if (zip != null) {
-                return "zip file \"" + zip + "\"";
+                return "zip file \"" + zip + "\"" +
+                       (dir != null && !dir.getPath().isEmpty() ? ", dir \"" + dir + "\"" : "");
             } else {
                 return "dex file \"" + dexFile + "\"";
             }
@@ -450,17 +473,40 @@
                  * (e.g. if the file isn't actually a zip/jar
                  * file).
                  */
-                System.logE("Unable to open zip file: " + file, ioe);
+                System.logE("Unable to open zip file: " + zip, ioe);
+                urlHandler = null;
             }
         }
 
+        public String findNativeLibrary(String name) {
+            maybeInit();
+
+            if (isDirectory) {
+                String path = new File(dir, name).getPath();
+                if (IoUtils.canOpenReadOnly(path)) {
+                    return path;
+                }
+            } else if (urlHandler != null) {
+                // Having a urlHandler means the element has a zip file.
+                // In this case Android supports loading the library iff
+                // it is stored in the zip uncompressed.
+
+                String entryName = new File(dir, name).getPath();
+                if (urlHandler.isEntryStored(entryName)) {
+                  return zip.getPath() + zipSeparator + entryName;
+                }
+            }
+
+            return null;
+        }
+
         public URL findResource(String name) {
             maybeInit();
 
             // We support directories so we can run tests and/or legacy code
             // that uses Class.getResource.
             if (isDirectory) {
-                File resourceFile = new File(file, name);
+                File resourceFile = new File(dir, name);
                 if (resourceFile.exists()) {
                     try {
                         return resourceFile.toURI().toURL();
diff --git a/luni/src/main/java/java/lang/Runtime.java b/luni/src/main/java/java/lang/Runtime.java
index ad549f2..f1f6438 100644
--- a/luni/src/main/java/java/lang/Runtime.java
+++ b/luni/src/main/java/java/lang/Runtime.java
@@ -358,11 +358,14 @@
      */
     void loadLibrary(String libraryName, ClassLoader loader) {
         if (loader != null) {
-            // TODO: We shouldn't assume that we know default linker search logic.
             String filename = loader.findLibrary(libraryName);
             if (filename == null) {
-                // The dynamic linker might still find the library by name.
-                filename = System.mapLibraryName(libraryName);
+                // It's not necessarily true that the ClassLoader used
+                // System.mapLibraryName, but the default setup does, and it's
+                // misleading to say we didn't find "libMyLibrary.so" when we
+                // actually searched for "liblibMyLibrary.so.so".
+                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
+                                               System.mapLibraryName(libraryName) + "\"");
             }
             String error = doLoad(filename, loader);
             if (error != null) {
@@ -424,19 +427,18 @@
         } else if (loader instanceof BaseDexClassLoader) {
             BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
             ldLibraryPath = dexClassLoader.getLdLibraryPath();
-            dexPath = dexClassLoader.getDexPath();
         }
         // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
         // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
         // internal natives.
         synchronized (this) {
-            return nativeLoad(name, loader, ldLibraryPath, dexPath);
+            return nativeLoad(name, loader, ldLibraryPath);
         }
     }
 
     // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
     private static native String nativeLoad(String filename, ClassLoader loader,
-            String ldLibraryPath, String dexPath);
+            String ldLibraryPath);
 
     /**
      * Provides a hint to the runtime that it would be useful to attempt
diff --git a/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java b/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
index 9c4a4e8..2f26b14 100644
--- a/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
+++ b/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
@@ -54,7 +54,7 @@
 
   /**
    * Returns a URL backed by this stream handler for the named resource, or {@code null} if the
-   * resource cannot be found under the exact name presented.
+   * entry cannot be found under the exact name presented.
    */
   public URL getEntryUrlOrNull(String entryName) {
     if (jarFile.findEntry(entryName) != null) {
@@ -69,6 +69,15 @@
     return null;
   }
 
+  /**
+   * Returns true if entry with specified name exists and stored (not compressed),
+   * and false otherwise.
+   */
+  public boolean isEntryStored(String entryName) {
+    ZipEntry entry = jarFile.findEntry(entryName);
+    return entry != null && entry.getMethod() == ZipEntry.STORED;
+  }
+
   @Override
   protected URLConnection openConnection(URL url) throws IOException {
     return new ClassPathURLConnection(url, jarFile);
diff --git a/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java b/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java
index 9eb92de..eb66c3a 100644
--- a/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java
+++ b/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java
@@ -37,6 +37,7 @@
     private static final String JAR = "ClassPathURLStreamHandlerTest.jar";
     private static final String ENTRY_IN_ROOT = "root.txt";
     private static final String ENTRY_IN_SUBDIR = "foo/bar/baz.txt";
+    private static final String ENTRY_STORED = "stored_file.txt";
     private static final String ENTRY_WITH_SPACES_ENCODED = "file%20with%20spaces.txt";
     private static final String ENTRY_WITH_SPACES_UNENCODED = "file with spaces.txt";
     private static final String ENTRY_THAT_NEEDS_ESCAPING = "file_with_percent20_%20.txt";
@@ -82,6 +83,16 @@
         streamHandler.close();
     }
 
+    public void testIsEntryStored() throws IOException {
+        String fileName = jarFile.getCanonicalPath();
+        ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
+
+        assertFalse(streamHandler.isEntryStored("this/file/does/not/exist.txt"));
+        // This one is compressed
+        assertFalse(streamHandler.isEntryStored(ENTRY_IN_SUBDIR));
+        assertTrue(streamHandler.isEntryStored(ENTRY_STORED));
+    }
+
     public void testOpenConnection() throws Exception {
         String fileName = jarFile.getCanonicalPath();
         ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
diff --git a/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar b/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar
index db67c93..ef810a7 100644
--- a/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar
+++ b/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar
Binary files differ