Merge changes Ia0623d38,Iaabd5d8b,I579bb12f,Ia9930edd

* changes:
  Fix splits class loader context for non dependant splits
  Encode the entire class loader context for dex2oat
  Add a command line option to optimize individual splits
  Refactor the arguments passed to dexopt invocations
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index e5e7b77..64d687e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -508,21 +508,13 @@
              in boolean isSharedModule, IDexModuleRegisterCallback callback);
 
     /**
-     * Ask the package manager to perform a dex-opt for the given reason. The package
-     * manager will map the reason to a compiler filter according to the current system
-     * configuration.
-     */
-    boolean performDexOpt(String packageName, boolean checkProfiles,
-            int compileReason, boolean force, boolean bootComplete, boolean downgrade);
-
-    /**
      * Ask the package manager to perform a dex-opt with the given compiler filter.
      *
      * Note: exposed only for the shell command to allow moving packages explicitly to a
      *       definite state.
      */
     boolean performDexOptMode(String packageName, boolean checkProfiles,
-            String targetCompilerFilter, boolean force, boolean bootComplete);
+            String targetCompilerFilter, boolean force, boolean bootComplete, String splitName);
 
     /**
      * Ask the package manager to perform a dex-opt with the given compiler filter on the
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 34092ad..914e81e 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -37,6 +37,7 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.LocalServices;
 import com.android.server.PinnerService;
+import com.android.server.pm.dex.DexoptOptions;
 
 import java.io.File;
 import java.util.Set;
@@ -217,12 +218,10 @@
             // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
             // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
             // trade-off worth doing to save boot time work.
-            int result = pm.performDexOptWithStatus(pkg,
-                    /* checkProfiles */ false,
+            int result = pm.performDexOptWithStatus(new DexoptOptions(
+                    pkg,
                     PackageManagerService.REASON_BOOT,
-                    /* force */ false,
-                    /* bootComplete */ true,
-                    /* downgrade */ false);
+                    DexoptOptions.DEXOPT_BOOT_COMPLETE));
             if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
                 updatedPackages.add(pkg);
             }
@@ -334,22 +333,22 @@
             // Optimize package if needed. Note that there can be no race between
             // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
             boolean success;
+            int dexoptFlags =
+                    DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                    DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
             if (is_for_primary_dex) {
-                int result = pm.performDexOptWithStatus(pkg,
-                        /* checkProfiles */ true,
-                        reason,
-                        false /* forceCompile*/,
-                        true /* bootComplete */,
-                        downgrade);
+                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg,
+                        PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                        dexoptFlags));
                 success = result != PackageDexOptimizer.DEX_OPT_FAILED;
                 if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
                     updatedPackages.add(pkg);
                 }
             } else {
-                success = pm.performDexOptSecondary(pkg,
-                        reason,
-                        false /* force */,
-                        downgrade);
+                success = pm.performDexOpt(new DexoptOptions(pkg,
+                        PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                        dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
             }
             if (success) {
                 // Dexopt succeeded, remove package from the list of failing ones.
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 4ff6cbf..241d76f 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -35,6 +34,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.DexoptOptions;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -314,19 +314,19 @@
             libraryDependencies = NO_LIBRARIES;
         }
 
+
         optimizer.performDexOpt(pkg, libraryDependencies,
-                null /* ISAs */, false /* checkProfiles */,
-                getCompilerFilterForReason(compilationReason),
+                null /* ISAs */,
                 null /* CompilerStats.PackageStats */,
                 mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName),
-                true /* bootComplete */,
-                false /* downgrade */);
+                new DexoptOptions(pkg.packageName, compilationReason,
+                        DexoptOptions.DEXOPT_BOOT_COMPLETE));
 
-        mPackageManagerService.getDexManager().dexoptSecondaryDex(pkg.packageName,
-                getCompilerFilterForReason(compilationReason),
-                false /* force */,
-                false /* compileOnlySharedDex */,
-                false /* downgrade */);
+        mPackageManagerService.getDexManager().dexoptSecondaryDex(
+                new DexoptOptions(pkg.packageName, compilationReason,
+                        DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                        DexoptOptions.DEXOPT_BOOT_COMPLETE));
+
         return commands;
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 644bab1..e53a08a 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -19,9 +19,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
-import android.os.Environment;
 import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -30,11 +28,12 @@
 import android.os.WorkSource;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.dex.DexoptUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -123,18 +122,16 @@
      * synchronized on {@link #mInstallLock}.
      */
     int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
-            String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
-            CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps,
-            boolean bootComplete, boolean downgrade) {
+            String[] instructionSets, CompilerStats.PackageStats packageStats,
+            boolean isUsedByOtherApps, DexoptOptions options) {
         if (!canOptimizePackage(pkg)) {
             return DEX_OPT_SKIPPED;
         }
         synchronized (mInstallLock) {
             final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid);
             try {
-                return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
-                        targetCompilationFilter, packageStats, isUsedByOtherApps, bootComplete,
-                        downgrade);
+                return performDexOptLI(pkg, sharedLibraries, instructionSets,
+                        packageStats, isUsedByOtherApps, options);
             } finally {
                 releaseWakeLockLI(acquireTime);
             }
@@ -147,9 +144,8 @@
      */
     @GuardedBy("mInstallLock")
     private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
-            String[] targetInstructionSets, boolean checkForProfileUpdates,
-            String targetCompilerFilter, CompilerStats.PackageStats packageStats,
-            boolean isUsedByOtherApps, boolean bootComplete, boolean downgrade) {
+            String[] targetInstructionSets, CompilerStats.PackageStats packageStats,
+            boolean isUsedByOtherApps, DexoptOptions options) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
@@ -157,16 +153,18 @@
         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
 
         final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
-                targetCompilerFilter, isUsedByOtherApps);
-        final boolean profileUpdated = checkForProfileUpdates &&
+                options.getCompilerFilter(), isUsedByOtherApps);
+        final boolean profileUpdated = options.isCheckForProfileUpdates() &&
                 isProfileUpdated(pkg, sharedGid, compilerFilter);
 
-        final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
         // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
-        final int dexoptFlags = getDexFlags(pkg, compilerFilter, bootComplete);
-        // Get the dependencies of each split in the package. For each code path in the package,
-        // this array contains the relative paths of each split it depends on, separated by colons.
-        String[] splitDependencies = getSplitDependencies(pkg);
+        final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
+
+        // Get the class loader context dependencies.
+        // For each code path in the package, this array contains the class loader context that
+        // needs to be passed to dexopt in order to ensure correct optimizations.
+        String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts(
+                pkg.applicationInfo, sharedLibraries);
 
         int result = DEX_OPT_SKIPPED;
         for (int i = 0; i < paths.size(); i++) {
@@ -177,17 +175,18 @@
             }
             // Append shared libraries with split dependencies for this split.
             String path = paths.get(i);
-            String sharedLibrariesPathWithSplits;
-            if (sharedLibrariesPath != null && splitDependencies[i] != null) {
-                sharedLibrariesPathWithSplits = sharedLibrariesPath + ":" + splitDependencies[i];
-            } else {
-                sharedLibrariesPathWithSplits =
-                        splitDependencies[i] != null ? splitDependencies[i] : sharedLibrariesPath;
+            if (options.getSplitName() != null) {
+                // We are asked to compile only a specific split. Check that the current path is
+                // what we are looking for.
+                if (!options.getSplitName().equals(new File(path).getName())) {
+                    continue;
+                }
             }
+
             for (String dexCodeIsa : dexCodeInstructionSets) {
-                int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated,
-                        sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats,
-                        downgrade);
+                int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
+                        profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
+                        packageStats, options.isDowngrade());
                 // The end result is:
                 //  - FAILED if any path failed,
                 //  - PERFORMED if at least one path needed compilation,
@@ -456,86 +455,6 @@
     }
 
     /**
-     * Computes the shared libraries path that should be passed to dexopt.
-     */
-    private String getSharedLibrariesPath(String[] sharedLibraries) {
-        if (sharedLibraries == null || sharedLibraries.length == 0) {
-            return null;
-        }
-        StringBuilder sb = new StringBuilder();
-        for (String lib : sharedLibraries) {
-            if (sb.length() != 0) {
-                sb.append(":");
-            }
-            sb.append(lib);
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Walks dependency tree and gathers the dependencies for each split in a split apk.
-     * The split paths are stored as relative paths, separated by colons.
-     */
-    private String[] getSplitDependencies(PackageParser.Package pkg) {
-        // Convert all the code paths to relative paths.
-        String baseCodePath = new File(pkg.baseCodePath).getParent();
-        List<String> paths = pkg.getAllCodePaths();
-        String[] splitDependencies = new String[paths.size()];
-        for (int i = 0; i < paths.size(); i++) {
-            File pathFile = new File(paths.get(i));
-            String fileName = pathFile.getName();
-            paths.set(i, fileName);
-
-            // 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);
-            }
-        }
-
-        // If there are no other dependencies, fill in the implicit dependency on the base apk.
-        SparseArray<int[]> dependencies = pkg.applicationInfo.splitDependencies;
-        if (dependencies == null) {
-            for (int i = 1; i < paths.size(); i++) {
-                splitDependencies[i] = paths.get(0);
-            }
-            return splitDependencies;
-        }
-
-        // Fill in the dependencies, skipping the base apk which has no dependencies.
-        for (int i = 1; i < dependencies.size(); i++) {
-            getParentDependencies(dependencies.keyAt(i), paths, dependencies, splitDependencies);
-        }
-
-        return splitDependencies;
-    }
-
-    /**
-     * Recursive method to generate dependencies for a particular split.
-     * The index is a key from the package's splitDependencies.
-     */
-    private String getParentDependencies(int index, List<String> paths,
-            SparseArray<int[]> dependencies, String[] splitDependencies) {
-        // The base apk is always first, and has no dependencies.
-        if (index == 0) {
-            return null;
-        }
-        // Return the result if we've computed the dependencies for this index already.
-        if (splitDependencies[index] != null) {
-            return splitDependencies[index];
-        }
-        // Get the dependencies for the parent of this index and append its path to it.
-        int parent = dependencies.get(index)[0];
-        String parentDependencies =
-                getParentDependencies(parent, paths, dependencies, splitDependencies);
-        String path = parentDependencies == null ? paths.get(parent) :
-                parentDependencies + ":" + paths.get(parent);
-        splitDependencies[index] = path;
-        return path;
-    }
-
-    /**
      * Checks if there is an update on the profile information of the {@code pkg}.
      * If the compiler filter is not profile guided the method returns false.
      *
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6d501bb..6b8a415 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -280,6 +280,7 @@
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.DexManager;
+import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
@@ -9394,22 +9395,23 @@
             // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
             // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
             // trade-off worth doing to save boot time work.
-            int primaryDexOptStaus = performDexOptTraced(pkg.packageName,
-                    false /* checkProfiles */,
+            int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
+            int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
+                    pkg.packageName,
                     compilerFilter,
-                    false /* force */,
-                    bootComplete,
-                    false /* downgrade */);
+                    dexoptFlags));
 
             boolean secondaryDexOptStatus = true;
             if (pkg.isSystemApp()) {
                 // Only dexopt shared secondary dex files belonging to system apps to not slow down
                 // too much boot after an OTA.
-                secondaryDexOptStatus = mDexManager.dexoptSecondaryDex(pkg.packageName,
+                int secondaryDexoptFlags = dexoptFlags |
+                        DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                        DexoptOptions.DEXOPT_ONLY_SHARED_DEX;
+                mDexManager.dexoptSecondaryDex(new DexoptOptions(
+                        pkg.packageName,
                         compilerFilter,
-                        false /* force */,
-                        true /* compileOnlySharedDex */,
-                        false /* downgrade */);
+                        secondaryDexoptFlags));
             }
 
             if (secondaryDexOptStatus) {
@@ -9495,19 +9497,53 @@
         }
     }
 
+    /**
+     * Ask the package manager to perform a dex-opt with the given compiler filter.
+     *
+     * Note: exposed only for the shell command to allow moving packages explicitly to a
+     *       definite state.
+     */
     @Override
-    public boolean performDexOpt(String packageName,
-            boolean checkProfiles, int compileReason, boolean force, boolean bootComplete,
-            boolean downgrade) {
+    public boolean performDexOptMode(String packageName,
+            boolean checkProfiles, String targetCompilerFilter, boolean force,
+            boolean bootComplete, String splitName) {
+        int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0) |
+                (force ? DexoptOptions.DEXOPT_FORCE : 0) |
+                (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
+        return performDexOpt(new DexoptOptions(packageName, targetCompilerFilter,
+                splitName, flags));
+    }
+
+    /**
+     * Ask the package manager to perform a dex-opt with the given compiler filter on the
+     * secondary dex files belonging to the given package.
+     *
+     * Note: exposed only for the shell command to allow moving packages explicitly to a
+     *       definite state.
+     */
+    @Override
+    public boolean performDexOptSecondary(String packageName, String compilerFilter,
+            boolean force) {
+        int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                (force ? DexoptOptions.DEXOPT_FORCE : 0);
+        return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags));
+    }
+
+    /*package*/ boolean performDexOpt(DexoptOptions options) {
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return false;
-        } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
+        } else if (isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
             return false;
         }
-        int dexoptStatus = performDexOptWithStatus(
-              packageName, checkProfiles, compileReason, force, bootComplete,
-              downgrade);
-        return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
+
+        if (options.isDexoptOnlySecondaryDex()) {
+            return mDexManager.dexoptSecondaryDex(options);
+        } else {
+            int dexoptStatus = performDexOptWithStatus(options);
+            return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
+        }
     }
 
     /**
@@ -9516,34 +9552,14 @@
      *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
      *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
      */
-    /* package */ int performDexOptWithStatus(String packageName,
-            boolean checkProfiles, int compileReason, boolean force, boolean bootComplete,
-            boolean downgrade) {
-        return performDexOptTraced(packageName, checkProfiles,
-                getCompilerFilterForReason(compileReason), force, bootComplete, downgrade);
+    /* package */ int performDexOptWithStatus(DexoptOptions options) {
+        return performDexOptTraced(options);
     }
 
-    @Override
-    public boolean performDexOptMode(String packageName,
-            boolean checkProfiles, String targetCompilerFilter, boolean force,
-            boolean bootComplete) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return false;
-        } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
-            return false;
-        }
-        int dexOptStatus = performDexOptTraced(packageName, checkProfiles,
-                targetCompilerFilter, force, bootComplete, false /* downgrade */);
-        return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
-    }
-
-    private int performDexOptTraced(String packageName,
-                boolean checkProfiles, String targetCompilerFilter, boolean force,
-                boolean bootComplete, boolean downgrade) {
+    private int performDexOptTraced(DexoptOptions options) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
         try {
-            return performDexOptInternal(packageName, checkProfiles,
-                    targetCompilerFilter, force, bootComplete, downgrade);
+            return performDexOptInternal(options);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -9551,12 +9567,10 @@
 
     // Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
     // if the package can now be considered up to date for the given filter.
-    private int performDexOptInternal(String packageName,
-                boolean checkProfiles, String targetCompilerFilter, boolean force,
-                boolean bootComplete, boolean downgrade) {
+    private int performDexOptInternal(DexoptOptions options) {
         PackageParser.Package p;
         synchronized (mPackages) {
-            p = mPackages.get(packageName);
+            p = mPackages.get(options.getPackageName());
             if (p == null) {
                 // Package could not be found. Report failure.
                 return PackageDexOptimizer.DEX_OPT_FAILED;
@@ -9567,8 +9581,7 @@
         long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mInstallLock) {
-                return performDexOptInternalWithDependenciesLI(p, checkProfiles,
-                        targetCompilerFilter, force, bootComplete, downgrade);
+                return performDexOptInternalWithDependenciesLI(p, options);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -9588,12 +9601,11 @@
     }
 
     private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
-            boolean checkProfiles, String targetCompilerFilter,
-            boolean force, boolean bootComplete, boolean downgrade) {
+            DexoptOptions options) {
         // Select the dex optimizer based on the force parameter.
         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
         //       allocate an object here.
-        PackageDexOptimizer pdo = force
+        PackageDexOptimizer pdo = options.isForce()
                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
                 : mPackageDexOptimizer;
 
@@ -9610,37 +9622,14 @@
             for (PackageParser.Package depPackage : deps) {
                 // TODO: Analyze and investigate if we (should) profile libraries.
                 pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
-                        false /* checkProfiles */,
-                        targetCompilerFilter,
                         getOrCreateCompilerPackageStats(depPackage),
                         true /* isUsedByOtherApps */,
-                        bootComplete,
-                        downgrade);
+                        options);
             }
         }
-        return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
-                targetCompilerFilter, getOrCreateCompilerPackageStats(p),
-                mDexManager.isUsedByOtherApps(p.packageName), bootComplete, downgrade);
-    }
-
-    // Performs dexopt on the used secondary dex files belonging to the given package.
-    // Returns true if all dex files were process successfully (which could mean either dexopt or
-    // skip). Returns false if any of the files caused errors.
-    @Override
-    public boolean performDexOptSecondary(String packageName, String compilerFilter,
-            boolean force) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return false;
-        } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
-            return false;
-        }
-        return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force,
-                false /* compileOnlySharedDex */, false /* downgrade */);
-    }
-
-    public boolean performDexOptSecondary(String packageName, int compileReason,
-            boolean force, boolean downgrade) {
-        return mDexManager.dexoptSecondaryDex(packageName, compileReason, force, downgrade);
+        return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets,
+                getOrCreateCompilerPackageStats(p),
+                mDexManager.isUsedByOtherApps(p.packageName), options);
     }
 
     /**
@@ -9816,11 +9805,11 @@
 
             // Whoever is calling forceDexOpt wants a compiled package.
             // Don't use profiles since that may cause compilation to be skipped.
-            final int res = performDexOptInternalWithDependenciesLI(pkg,
-                    false /* checkProfiles */, getDefaultCompilerFilter(),
-                    true /* force */,
-                    true /* bootComplete */,
-                    false /* downgrade */);
+            final int res = performDexOptInternalWithDependenciesLI(
+                    pkg,
+                    new DexoptOptions(packageName,
+                            getDefaultCompilerFilter(),
+                            DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
 
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -18284,13 +18273,14 @@
                 // method because `pkg` may not be in `mPackages` yet.
                 //
                 // Also, don't fail application installs if the dexopt step fails.
+                DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName,
+                        REASON_INSTALL,
+                        DexoptOptions.DEXOPT_BOOT_COMPLETE);
                 mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
-                        null /* instructionSets */, false /* checkProfiles */,
-                        getCompilerFilterForReason(REASON_INSTALL),
+                        null /* instructionSets */,
                         getOrCreateCompilerPackageStats(pkg),
                         mDexManager.isUsedByOtherApps(pkg.packageName),
-                        true /* bootComplete */,
-                        false /* downgrade */);
+                        dexoptOptions);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 20d7b28..10ceba4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -365,6 +365,7 @@
         String compilationReason = null;
         String checkProfilesRaw = null;
         boolean secondaryDex = false;
+        String split = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -395,6 +396,9 @@
                 case "--secondary-dex":
                     secondaryDex = true;
                     break;
+                case "--split":
+                    split = getNextArgRequired();
+                    break;
                 default:
                     pw.println("Error: Unknown option: " + opt);
                     return 1;
@@ -423,6 +427,16 @@
             return 1;
         }
 
+        if (allPackages && split != null) {
+            pw.println("-a cannot be specified together with --split");
+            return 1;
+        }
+
+        if (secondaryDex && split != null) {
+            pw.println("--secondary-dex cannot be specified together with --split");
+            return 1;
+        }
+
         String targetCompilerFilter;
         if (compilerFilter != null) {
             if (!DexFile.isValidCompilerFilter(compilerFilter)) {
@@ -472,7 +486,7 @@
                             targetCompilerFilter, forceCompilation)
                     : mInterface.performDexOptMode(packageName,
                             checkProfiles, targetCompilerFilter, forceCompilation,
-                            true /* bootComplete */);
+                            true /* bootComplete */, split);
             if (!result) {
                 failedPackages.add(packageName);
             }
@@ -1609,7 +1623,7 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println("");
-        pw.println("  compile [-m MODE | -r REASON] [-f] [-c]");
+        pw.println("  compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
         pw.println("          [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
         pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".");
         pw.println("    Options:");
@@ -1635,6 +1649,7 @@
         pw.println("      --reset: restore package to its post-install state");
         pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
         pw.println("      --secondary-dex: compile app secondary dex files");
+        pw.println("      --split SPLIT: compile only the given split name");
         pw.println("  bg-dexopt-job");
         pw.println("    Execute the background optimizations immediately.");
         pw.println("    Note that the command only runs the background optimizer logic. It may");
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 441994d..0d4df4d 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -300,33 +300,21 @@
     }
 
     /**
-     * Perform dexopt on the package {@code packageName} secondary dex files.
+     * Perform dexopt on with the given {@code options} on the secondary dex files.
      * @return true if all secondary dex files were processed successfully (compiled or skipped
      *         because they don't need to be compiled)..
      */
-    public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force,
-            boolean downgrade) {
-        return dexoptSecondaryDex(packageName,
-                PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason),
-                force, /* compileOnlySharedDex */ false, downgrade);
-    }
-
-    /**
-     * Perform dexopt on the package {@code packageName} secondary dex files.
-     * @return true if all secondary dex files were processed successfully (compiled or skipped
-     *         because they don't need to be compiled)..
-     */
-    public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force,
-            boolean compileOnlySharedDex, boolean downgrade) {
+    public boolean dexoptSecondaryDex(DexoptOptions options) {
         // Select the dex optimizer based on the force parameter.
         // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
         // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
         // passing the force flag through the multitude of layers.
         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
         //       allocate an object here.
-        PackageDexOptimizer pdo = force
+        PackageDexOptimizer pdo = options.isForce()
                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
                 : mPackageDexOptimizer;
+        String packageName = options.getPackageName();
         PackageUseInfo useInfo = getPackageUseInfo(packageName);
         if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
@@ -339,7 +327,7 @@
         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
             String dexPath = entry.getKey();
             DexUseInfo dexUseInfo = entry.getValue();
-            if (compileOnlySharedDex && !dexUseInfo.isUsedByOtherApps()) {
+            if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
                 continue;
             }
             PackageInfo pkg = null;
@@ -361,8 +349,8 @@
             }
 
             int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
-                    dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps(),
-                    downgrade);
+                    dexUseInfo.getLoaderIsas(), options.getCompilerFilter(),
+                    dexUseInfo.isUsedByOtherApps(), options.isDowngrade());
             success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
         }
         return success;
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
new file mode 100644
index 0000000..f57cf5e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 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.server.pm.dex;
+
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
+
+import android.annotation.Nullable;
+
+/**
+ * Options used for dexopt invocations.
+ */
+public final class DexoptOptions {
+    // When set, the profiles will be checked for updates before calling dexopt. If
+    // the apps profiles didn't update in a meaningful way (decided by the compiler), dexopt
+    // will be skipped.
+    // Currently this only affects the optimization of primary apks. Secondary dex files
+    // will always check the profiles for updates.
+    public static final int DEXOPT_CHECK_FOR_PROFILES_UPDATES = 1 << 0;
+
+    // When set, dexopt will execute unconditionally (even if not needed).
+    public static final int DEXOPT_FORCE = 1 << 1;
+
+    // Whether or not the invocation of dexopt is done after the boot is completed. This is used
+    // in order to adjust the priority of the compilation thread.
+    public static final int DEXOPT_BOOT_COMPLETE = 1 << 2;
+
+    // When set, the dexopt invocation will optimize only the secondary dex files. If false, dexopt
+    // will only consider the primary apk.
+    public static final int DEXOPT_ONLY_SECONDARY_DEX = 1 << 3;
+
+    // When set, dexopt will optimize only dex files that are used by other apps.
+    // Currently, this flag is ignored for primary apks.
+    public static final int DEXOPT_ONLY_SHARED_DEX = 1 << 4;
+
+    // When set, dexopt will attempt to scale down the optimizations previously applied in order
+    // save disk space.
+    public static final int DEXOPT_DOWNGRADE = 1 << 5;
+
+    // The name of package to optimize.
+    private final String mPackageName;
+
+    // The intended target compiler filter. Note that dexopt might adjust the filter before the
+    // execution based on factors like: vmSafeMode and packageUsedByOtherApps.
+    private final String mCompilerFilter;
+
+    // The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags.
+    private final int mFlags;
+
+    // When not null, dexopt will optimize only the split identified by this name.
+    // It only applies for primary apk and it's always null if mOnlySecondaryDex is true.
+    private final String mSplitName;
+
+    public DexoptOptions(String packageName, String compilerFilter, int flags) {
+        this(packageName, compilerFilter, /*splitName*/ null, flags);
+    }
+
+    public DexoptOptions(String packageName, int compilerReason, int flags) {
+        this(packageName, getCompilerFilterForReason(compilerReason), flags);
+    }
+
+    public DexoptOptions(String packageName, String compilerFilter, String splitName, int flags) {
+        int validityMask =
+                DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                DEXOPT_FORCE |
+                DEXOPT_BOOT_COMPLETE |
+                DEXOPT_ONLY_SECONDARY_DEX |
+                DEXOPT_ONLY_SHARED_DEX |
+                DEXOPT_DOWNGRADE;
+        if ((flags & (~validityMask)) != 0) {
+            throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
+        }
+
+        mPackageName = packageName;
+        mCompilerFilter = compilerFilter;
+        mFlags = flags;
+        mSplitName = splitName;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public boolean isCheckForProfileUpdates() {
+        return (mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) != 0;
+    }
+
+    public String getCompilerFilter() {
+        return mCompilerFilter;
+    }
+
+    public boolean isForce() {
+        return (mFlags & DEXOPT_FORCE) != 0;
+    }
+
+    public boolean isBootComplete() {
+        return (mFlags & DEXOPT_BOOT_COMPLETE) != 0;
+    }
+
+    public boolean isDexoptOnlySecondaryDex() {
+        return (mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0;
+    }
+
+    public boolean isDexoptOnlySharedDex() {
+        return (mFlags & DEXOPT_ONLY_SHARED_DEX) != 0;
+    }
+
+    public boolean isDowngrade() {
+        return (mFlags & DEXOPT_DOWNGRADE) != 0;
+    }
+
+    public String getSplitName() {
+        return mSplitName;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
new file mode 100644
index 0000000..abac52f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2017 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.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";
+
+    private DexoptUtils() {}
+
+    /**
+     * Creates the class loader context dependencies for each of the application code paths.
+     * The returned array contains the class loader contexts that needs to be passed to dexopt in
+     * order to ensure correct optimizations.
+     *
+     * A class loader context describes how the class loader chain should be built by dex2oat
+     * in order to ensure that classes are resolved during compilation as they would be resolved
+     * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is
+     * loaded in a different context (with a different set of class loaders or a different
+     * classpath), the compiled code will be rejected.
+     *
+     * Note that the class loader context only includes dependencies and not the code path itself.
+     * The contexts are created based on the application split dependency list and
+     * the provided shared libraries.
+     *
+     * All the code paths encoded in the context will be relative to the base directory. This
+     * enables stage compilation where compiler artifacts may be moved around.
+     *
+     * The result is indexed as follows:
+     *   - 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.
+        String sharedLibrariesClassPath = encodeClasspath(sharedLibraries);
+        String baseApkContextClassLoader = encodeClassLoader(
+                sharedLibrariesClassPath, "dalvik.system.PathClassLoader");
+
+        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 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 + splitRelativeCodePaths.length];
+        classLoaderContexts[0] = baseApkContextClassLoader;
+
+        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] = 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
+            // split. We do this recursively and store intermediate results in classLoaderContexts.
+
+            // First, look at the split class loaders and cache their individual contexts (i.e.
+            // the class loader + the name of the split). This is an optimization to avoid
+            // re-computing them during the recursive call.
+            // The cache is stored in splitClassLoaderEncodingCache. The difference between this and
+            // 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[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
+            for (int i = 0; i < splitRelativeCodePaths.length; i++) {
+                splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
+                        "dalvik.system.PathClassLoader");
+            }
+            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]);
+            }
+        }
+
+        return classLoaderContexts;
+    }
+
+    /**
+     * Recursive method to generate the class loader context dependencies for the split with the
+     * given index. {@param classLoaderContexts} acts as an accumulator. Upton return
+     * {@code classLoaderContexts[index]} will contain the split dependency.
+     * During computation, the method may resolve the dependencies of other splits as it traverses
+     * the entire parent chain. The result will also be stored in {@param classLoaderContexts}.
+     *
+     * Note that {@code index 0} denotes the base apk and it is special handled. When the
+     * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}.
+     * {@code classLoaderContexts[0]} is not modified in this method.
+     *
+     * @param index the index of the split (Note that index 0 denotes the base apk)
+     * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits.
+     *    It contains only the split class loader and not the the base. The split
+     *    with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}.
+     * @param splitDependencies the dependencies for all splits. Note that in this array index 0
+     *    is the base and splits start from index 1.
+     * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits
+     *    start at index 1.
+     * @param splitDependencyOnBase the encoding of the implicit split dependency on base.
+     */
+    private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache,
+            SparseArray<int[]> splitDependencies, String[] classLoaderContexts,
+            String splitDependencyOnBase) {
+        // If we hit the base apk return its custom dependency list which is
+        // sharedLibraries + base.apk
+        if (index == 0) {
+            return splitDependencyOnBase;
+        }
+        // Return the result if we've computed the splitDependencies for this index already.
+        if (classLoaderContexts[index] != null) {
+            return classLoaderContexts[index];
+        }
+        // Get the splitDependencies for the parent of this index and append its path to it.
+        int parent = splitDependencies.get(index)[0];
+        String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache,
+                splitDependencies, classLoaderContexts, splitDependencyOnBase);
+
+        // The split context is: `parent context + parent dependencies context`.
+        String splitContext = (parent == 0) ?
+                parentDependencies :
+                encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies);
+        classLoaderContexts[index] = splitContext;
+        return splitContext;
+    }
+
+    /**
+     * Encodes the shared libraries classpathElements in a format accepted by dexopt.
+     * NOTE: Keep this in sync with the dexopt expectations! Right now that is
+     * a list separated by ':'.
+     */
+    private static String encodeClasspath(String[] classpathElements) {
+        if (classpathElements == null || classpathElements.length == 0) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (String element : classpathElements) {
+            if (sb.length() != 0) {
+                sb.append(":");
+            }
+            sb.append(element);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Adds an element to the encoding of an existing classpath.
+     * {@see PackageDexOptimizer.encodeClasspath(String[])}
+     */
+    private static String encodeClasspath(String classpath, String newElement) {
+        return classpath.isEmpty() ? newElement : (classpath + ":" + newElement);
+    }
+
+    /**
+     * Encodes a single class loader dependency starting from {@param path} and
+     * {@param classLoaderName}.
+     * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
+     * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
+     */
+    private static String encodeClassLoader(String classpath, String classLoaderName) {
+        String classLoaderDexoptEncoding = classLoaderName;
+        if ("dalvik.system.PathClassLoader".equals(classLoaderName)) {
+            classLoaderDexoptEncoding = "PCL";
+        } else {
+            Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName);
+        }
+        return classLoaderDexoptEncoding + "[" + classpath + "]";
+    }
+
+    /**
+     * Links to dependencies together in a format accepted by dexopt.
+     * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
+     * dependencies {@see encodeClassLoader} separated by ';'.
+     */
+    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/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
new file mode 100644
index 0000000..1eb5552
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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.server.pm.dex;
+
+
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageManagerServiceCompilerMapping;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DexoptOptionsTests {
+    private final static String mPackageName = "test.android.com";
+    private final static String mCompilerFilter =
+            PackageManagerServiceCompilerMapping.getDefaultCompilerFilter();
+    private final static String mSplitName = "split-A.apk";
+
+    @Test
+    public void testCreateDexoptOptionsEmpty() {
+        DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, /*flags*/ 0);
+        assertEquals(mPackageName, opt.getPackageName());
+        assertEquals(mCompilerFilter, opt.getCompilerFilter());
+        assertEquals(null, opt.getSplitName());
+        assertFalse(opt.isBootComplete());
+        assertFalse(opt.isCheckForProfileUpdates());
+        assertFalse(opt.isDexoptOnlySecondaryDex());
+        assertFalse(opt.isDexoptOnlySharedDex());
+        assertFalse(opt.isDowngrade());
+        assertFalse(opt.isForce());
+    }
+
+    @Test
+    public void testCreateDexoptOptionsFull() {
+        int flags =
+                DexoptOptions.DEXOPT_FORCE |
+                DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                DexoptOptions.DEXOPT_ONLY_SHARED_DEX |
+                DexoptOptions.DEXOPT_DOWNGRADE;
+
+        DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags);
+        assertEquals(mPackageName, opt.getPackageName());
+        assertEquals(mCompilerFilter, opt.getCompilerFilter());
+        assertEquals(null, opt.getSplitName());
+        assertTrue(opt.isBootComplete());
+        assertTrue(opt.isCheckForProfileUpdates());
+        assertTrue(opt.isDexoptOnlySecondaryDex());
+        assertTrue(opt.isDexoptOnlySharedDex());
+        assertTrue(opt.isDowngrade());
+        assertTrue(opt.isForce());
+    }
+
+    @Test
+    public void testCreateDexoptOptionsReason() {
+        int flags =
+                DexoptOptions.DEXOPT_FORCE |
+                DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+
+        int[] reasons = new int[] {
+                PackageManagerService.REASON_FIRST_BOOT,
+                PackageManagerService.REASON_BOOT,
+                PackageManagerService.REASON_INSTALL,
+                PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                PackageManagerService.REASON_AB_OTA,
+                PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE};
+
+        for (int reason : reasons) {
+            DexoptOptions opt = new DexoptOptions(mPackageName, reason, flags);
+            assertEquals(mPackageName, opt.getPackageName());
+            assertEquals(getCompilerFilterForReason(reason), opt.getCompilerFilter());
+            assertEquals(null, opt.getSplitName());
+            assertTrue(opt.isBootComplete());
+            assertTrue(opt.isCheckForProfileUpdates());
+            assertFalse(opt.isDexoptOnlySecondaryDex());
+            assertFalse(opt.isDexoptOnlySharedDex());
+            assertFalse(opt.isDowngrade());
+            assertTrue(opt.isForce());
+        }
+    }
+
+    @Test
+    public void testCreateDexoptOptionsSplit() {
+        int flags = DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE;
+
+        DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, mSplitName, flags);
+        assertEquals(mPackageName, opt.getPackageName());
+        assertEquals(mCompilerFilter, opt.getCompilerFilter());
+        assertEquals(mSplitName, opt.getSplitName());
+        assertTrue(opt.isBootComplete());
+        assertFalse(opt.isCheckForProfileUpdates());
+        assertFalse(opt.isDexoptOnlySecondaryDex());
+        assertFalse(opt.isDexoptOnlySharedDex());
+        assertFalse(opt.isDowngrade());
+        assertTrue(opt.isForce());
+    }
+
+    @Test
+    public void testCreateDexoptInvalid() {
+        boolean gotException = false;
+        try {
+            int invalidFlags = 999;
+            new DexoptOptions(mPackageName, mCompilerFilter, invalidFlags);
+        } catch (IllegalArgumentException ignore) {
+            gotException = true;
+        }
+
+        assertTrue(gotException);
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..21b286e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 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.server.pm.dex;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.pm.ApplicationInfo;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.PathClassLoader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DexoptUtilsTest {
+    private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName();
+    private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
+            DelegateLastClassLoader.class.getName();
+
+    private ApplicationInfo createMockApplicationInfo(String baseClassLoader, boolean addSplits,
+            boolean addSplitDependencies) {
+        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[]{
+                    codeDir + "/base-1.dex",
+                    codeDir + "/base-2.dex",
+                    codeDir + "/base-3.dex",
+                    codeDir + "/base-4.dex",
+                    codeDir + "/base-5.dex",
+                    codeDir + "/base-6.dex"});
+
+            if (addSplitDependencies) {
+                ai.splitDependencies = new SparseArray<>(6 + 1);
+                ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency
+                ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2
+                ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4
+                ai.splitDependencies.put(3, new int[] {4}); // split 3 depends on 4
+                ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base
+                ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base
+                ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5
+            }
+        }
+        return ai;
+    }
+
+    @Test
+    public void testSplitChain() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        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-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]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
+        assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+    }
+
+    @Test
+    public void testSplitChainNoSplitDependencies() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+        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);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+        assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[base.dex]", contexts[1]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[2]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[3]);
+        assertEquals("PCL[];PCL[base.dex]", contexts[4]);
+        assertEquals("PCL[];PCL[base.dex]", contexts[5]);
+        assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]", contexts[6]);
+    }
+
+    @Test
+    public void testSplitChainWithNullPrimaryClassLoader() {
+        // A null classLoaderName should mean PathClassLoader.
+        ApplicationInfo ai = createMockApplicationInfo(null, true, true);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        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-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]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
+        assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+    }
+
+    @Test
+    public void tesNoSplits() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitsNullClassLoaderName() {
+        ApplicationInfo ai = createMockApplicationInfo(null, false, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitDelegateLast() {
+        ApplicationInfo ai = createMockApplicationInfo(
+                DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitsNoSharedLibraries() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitDelegateLastNoSharedLibraries() {
+        ApplicationInfo ai = createMockApplicationInfo(
+                DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+    }
+}