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]);
+ }
+}