Fix splits class loader context for non dependant splits

If the app doesn't request for splits to be loaded in isolation or does
not declare inter-split dependencies, then all the splits are loaded in
the base apk class loader (in the order of they are defined).

Fix the class loader context passed to dex2oat to reflect the runtime
loading logic.

(cherry picked from commit 305aeea38f96f0b94ad4be5cb979dd9cf98cf7df)

Bug: 38138251
Test: runtest -x
services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java

Merged-In: Ia0623d38883ae244fd16c0afb053fef016bf260a
Change-Id: Ia0623d38883ae244fd16c0afb053fef016bf260a
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index c750f65..abac52f 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -16,12 +16,12 @@
 
 package com.android.server.pm.dex;
 
-
 import android.content.pm.ApplicationInfo;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import java.io.File;
+import java.util.List;
 
 public final class DexoptUtils {
     private static final String TAG = "DexoptUtils";
@@ -50,6 +50,11 @@
      *   - index 0 contains the context for the base apk
      *   - index 1 to n contain the context for the splits in the order determined by
      *     {@code info.getSplitCodePaths()}
+     *
+     * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk}
+     * and pay attention to the way the classpath is created for the non isolated mode in:
+     * {@link android.app.LoadedApk#makePaths(
+     * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
      */
     public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) {
         // The base class loader context contains only the shared library.
@@ -57,35 +62,36 @@
         String baseApkContextClassLoader = encodeClassLoader(
                 sharedLibrariesClassPath, "dalvik.system.PathClassLoader");
 
-        String[] splitCodePaths = info.getSplitCodePaths();
-
-        if (splitCodePaths == null) {
+        if (info.getSplitCodePaths() == null) {
             // The application has no splits.
             return new String[] {baseApkContextClassLoader};
         }
 
         // The application has splits. Compute their class loader contexts.
 
+        // First, cache the relative paths of the splits and do some sanity checks
+        String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info);
+
         // The splits have an implicit dependency on the base apk.
         // This means that we have to add the base apk file in addition to the shared libraries.
         String baseApkName = new File(info.getBaseCodePath()).getName();
-        String splitDependencyOnBase = encodeClassLoader(
-                encodeClasspath(sharedLibrariesClassPath, baseApkName),
-                "dalvik.system.PathClassLoader");
+        String sharedLibrariesAndBaseClassPath =
+                encodeClasspath(sharedLibrariesClassPath, baseApkName);
 
         // The result is stored in classLoaderContexts.
         // Index 0 is the class loaded context for the base apk.
         // Index `i` is the class loader context encoding for split `i`.
-        String[] classLoaderContexts = new String[/*base apk*/ 1 + splitCodePaths.length];
+        String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
         classLoaderContexts[0] = baseApkContextClassLoader;
 
-        SparseArray<int[]> splitDependencies = info.splitDependencies;
-
-        if (splitDependencies == null) {
-            // If there are no inter-split dependencies, populate the result with the implicit
-            // dependency on the base apk.
+        if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) {
+            // If the app didn't request for the splits to be loaded in isolation or if it does not
+            // declare inter-split dependencies, then all the splits will be loaded in the base
+            // apk class loader (in the order of their definition).
+            String classpath = sharedLibrariesAndBaseClassPath;
             for (int i = 1; i < classLoaderContexts.length; i++) {
-                classLoaderContexts[i] = splitDependencyOnBase;
+                classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader");
+                classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
             }
         } else {
             // In case of inter-split dependencies, we need to walk the dependency chain of each
@@ -98,33 +104,27 @@
             // classLoaderContexts is that the later contains the full chain of class loaders for
             // a given split while splitClassLoaderEncodingCache only contains a single class loader
             // encoding.
-            String baseCodePath = new File(info.getBaseCodePath()).getParent();
-            String[] splitClassLoaderEncodingCache = new String[splitCodePaths.length];
-            for (int i = 0; i < splitCodePaths.length; i++) {
-                File pathFile = new File(splitCodePaths[i]);
-                String fileName = pathFile.getName();
-                splitClassLoaderEncodingCache[i] = encodeClassLoader(fileName,
+            String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
+            for (int i = 0; i < splitRelativeCodePaths.length; i++) {
+                splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
                         "dalvik.system.PathClassLoader");
-                // Sanity check that the base paths of the splits are all the same.
-                String basePath = pathFile.getParent();
-                if (!basePath.equals(baseCodePath)) {
-                    Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
-                            baseCodePath);
-                }
             }
+            String splitDependencyOnBase = encodeClassLoader(
+                    sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader");
+            SparseArray<int[]> splitDependencies = info.splitDependencies;
             for (int i = 1; i < splitDependencies.size(); i++) {
                 getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache,
                         splitDependencies, classLoaderContexts, splitDependencyOnBase);
             }
-        }
 
-        // At this point classLoaderContexts contains only the parent dependencies.
-        // We also need to add the class loader of the current split which should
-        // come first in the context.
-        for (int i = 1; i < classLoaderContexts.length; i++) {
-            String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader");
-            classLoaderContexts[i] = encodeClassLoaderChain(
-                    splitClassLoader, classLoaderContexts[i]);
+            // At this point classLoaderContexts contains only the parent dependencies.
+            // We also need to add the class loader of the current split which should
+            // come first in the context.
+            for (int i = 1; i < classLoaderContexts.length; i++) {
+                String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader");
+                classLoaderContexts[i] = encodeClassLoaderChain(
+                        splitClassLoader, classLoaderContexts[i]);
+            }
         }
 
         return classLoaderContexts;
@@ -227,4 +227,25 @@
     private static String encodeClassLoaderChain(String cl1, String cl2) {
         return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2);
     }
+
+    /**
+     * Returns the relative paths of the splits declared by the application {@code info}.
+     * Assumes that the application declares a non-null array of splits.
+     */
+    private static String[] getSplitRelativeCodePaths(ApplicationInfo info) {
+        String baseCodePath = new File(info.getBaseCodePath()).getParent();
+        String[] splitCodePaths = info.getSplitCodePaths();
+        String[] splitRelativeCodePaths = new String[splitCodePaths.length];
+        for (int i = 0; i < splitCodePaths.length; i++) {
+            File pathFile = new File(splitCodePaths[i]);
+            splitRelativeCodePaths[i] = pathFile.getName();
+            // Sanity check that the base paths of the splits are all the same.
+            String basePath = pathFile.getParent();
+            if (!basePath.equals(baseCodePath)) {
+                Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
+                        baseCodePath);
+            }
+        }
+        return splitRelativeCodePaths;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
index f1f1767..21b286e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -41,6 +41,7 @@
         ApplicationInfo ai = new ApplicationInfo();
         String codeDir = "/data/app/mock.android.com";
         ai.setBaseCodePath(codeDir + "/base.dex");
+        ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
 
         if (addSplits) {
             ai.setSplitCodePaths(new String[]{
@@ -73,7 +74,8 @@
 
         assertEquals(7, contexts.length);
         assertEquals("PCL[a.dex:b.dex]", contexts[0]);
-        assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]);
+        assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]",
+                contexts[1]);
         assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]);
         assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]);
         assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
@@ -89,15 +91,36 @@
 
         assertEquals(7, contexts.length);
         assertEquals("PCL[a.dex:b.dex]", contexts[0]);
-        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[1]);
-        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[2]);
-        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[3]);
-        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
-        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
-        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+        assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]);
+        assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]);
+        assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex]", contexts[3]);
+        assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
+        assertEquals(
+                "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]",
+                contexts[5]);
+        assertEquals(
+                "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
+                contexts[6]);
     }
 
     @Test
+    public void testSplitChainNoIsolationNoSharedLibrary() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+        ai.privateFlags = ai.privateFlags & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+        assertEquals("PCL[base.dex]", contexts[1]);
+        assertEquals("PCL[base.dex:base-1.dex]", contexts[2]);
+        assertEquals("PCL[base.dex:base-1.dex:base-2.dex]", contexts[3]);
+        assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
+        assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]);
+        assertEquals(
+                "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
+                contexts[6]);
+    }
+    @Test
     public void testSplitChainNoSharedLibraries() {
         ApplicationInfo ai = createMockApplicationInfo(
                 DELEGATE_LAST_CLASS_LOADER_NAME, true, true);