diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index e5e7b779ff6ae..64d687e9d3dea 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -507,14 +507,6 @@ interface IPackageManager { oneway void registerDexModule(in String packageName, in String dexModulePath, 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. * @@ -522,7 +514,7 @@ interface IPackageManager { * 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 34092ad6607a5..914e81e64ef69 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 android.util.Log; 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 @@ public class BackgroundDexOptService extends JobService { // 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 @@ public class BackgroundDexOptService extends JobService { // 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 4ff6cbf1e1c88..241d76f8a0ac3 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 @@ package com.android.server.pm; 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 android.util.Slog; 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 @@ public class OtaDexoptService extends IOtaDexopt.Stub { 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( + new DexoptOptions(pkg.packageName, compilationReason, + DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_BOOT_COMPLETE)); - mPackageManagerService.getDexManager().dexoptSecondaryDex(pkg.packageName, - getCompilerFilterForReason(compilationReason), - false /* force */, - false /* compileOnlySharedDex */, - false /* downgrade */); 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 644bab1fc2603..e53a08a8f3d4c 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 @@ package com.android.server.pm; 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.UserHandle; 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 @@ public class PackageDexOptimizer { * 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 @@ public class PackageDexOptimizer { */ @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 @@ public class PackageDexOptimizer { 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 @@ public class PackageDexOptimizer { } // 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, @@ -455,86 +454,6 @@ public class PackageDexOptimizer { return adjustDexoptNeeded(dexoptNeeded); } - /** - * 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 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 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 paths, - SparseArray 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 6d501bb94299d..6b8a415364871 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.PermissionsState.PermissionState; 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * 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 @@ public class PackageManagerService extends IPackageManager.Stub * {@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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub } 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 20d7b28c55e15..10ceba418f224 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 @@ class PackageManagerShellCommand extends ShellCommand { String compilationReason = null; String checkProfilesRaw = null; boolean secondaryDex = false; + String split = null; String opt; while ((opt = getNextOption()) != null) { @@ -395,6 +396,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--secondary-dex": secondaryDex = true; break; + case "--split": + split = getNextArgRequired(); + break; default: pw.println("Error: Unknown option: " + opt); return 1; @@ -423,6 +427,16 @@ class PackageManagerShellCommand extends ShellCommand { 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 @@ class PackageManagerShellCommand extends ShellCommand { targetCompilerFilter, forceCompilation) : mInterface.performDexOptMode(packageName, checkProfiles, targetCompilerFilter, forceCompilation, - true /* bootComplete */); + true /* bootComplete */, split); if (!result) { failedPackages.add(packageName); } @@ -1609,7 +1623,7 @@ class PackageManagerShellCommand extends ShellCommand { 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 @@ class PackageManagerShellCommand extends ShellCommand { 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 441994d8c590d..0d4df4d4c9b4a 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 @@ public class DexManager { } /** - * 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 @@ public class DexManager { for (Map.Entry 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 @@ public class DexManager { } 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 0000000000000..f57cf5e2b9996 --- /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 0000000000000..abac52f7a8daa --- /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 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 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 0000000000000..1eb5552d533f5 --- /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 0000000000000..21b286e68adfc --- /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]); + } +}