Implement batch optimization.
Bug: 255518446
Bug: 244714876
Test: atest ArtServiceTests
Test: adb shell pm art optimize-packages bg-dexopt
Ignore-AOSP-First: ART Services.
Change-Id: I27fb4b009615f4f6b1284c30299a24ef956a78bf
diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
index f2b4819..1003030 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -4,6 +4,7 @@
public final class ArtManagerLocal {
ctor @Deprecated public ArtManagerLocal();
ctor public ArtManagerLocal(@NonNull android.content.Context);
+ method public void clearOptimizePackagesCallback();
method @NonNull public com.android.server.art.model.DeleteResult deleteOptimizedArtifacts(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
method @NonNull public com.android.server.art.model.DeleteResult deleteOptimizedArtifacts(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, int);
method @NonNull public com.android.server.art.model.OptimizationStatus getOptimizationStatus(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String);
@@ -12,6 +13,11 @@
method public void notifyDexContainersLoaded(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>);
method @NonNull public com.android.server.art.model.OptimizeResult optimizePackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.OptimizeParams);
method @NonNull public com.android.server.art.model.OptimizeResult optimizePackage(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull com.android.server.art.model.OptimizeParams, @NonNull android.os.CancellationSignal);
+ method public void setOptimizePackagesCallback(@NonNull java.util.concurrent.Executor, @NonNull com.android.server.art.ArtManagerLocal.OptimizePackagesCallback);
+ }
+
+ public static interface ArtManagerLocal.OptimizePackagesCallback {
+ method public void onOverrideBatchOptimizeParams(@NonNull com.android.server.pm.PackageManagerLocal.FilteredSnapshot, @NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull com.android.server.art.model.BatchOptimizeParams.Builder);
}
public class ReasonMapping {
@@ -47,6 +53,17 @@
field public static final int PRIORITY_INTERACTIVE_FAST = 80; // 0x50
}
+ public abstract class BatchOptimizeParams {
+ method @NonNull public abstract com.android.server.art.model.OptimizeParams getOptimizeParams();
+ method @NonNull public abstract java.util.List<java.lang.String> getPackages();
+ }
+
+ public static final class BatchOptimizeParams.Builder {
+ method @NonNull public com.android.server.art.model.BatchOptimizeParams build();
+ method @NonNull public com.android.server.art.model.BatchOptimizeParams.Builder setOptimizeParams(@NonNull com.android.server.art.model.OptimizeParams);
+ method @NonNull public com.android.server.art.model.BatchOptimizeParams.Builder setPackages(@NonNull java.util.List<java.lang.String>);
+ }
+
public class DeleteResult {
method public long getFreedBytes();
}
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 1685174..cc64f71 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -18,15 +18,19 @@
import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.ReasonMapping.BatchOptimizeReason;
import static com.android.server.art.Utils.Abi;
import static com.android.server.art.model.ArtFlags.DeleteFlags;
import static com.android.server.art.model.ArtFlags.GetStatusFlags;
+import static com.android.server.art.model.Config.Callback;
import static com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.apphibernation.AppHibernationManager;
import android.content.Context;
import android.os.Binder;
import android.os.CancellationSignal;
@@ -37,6 +41,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalManagerRegistry;
import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.BatchOptimizeParams;
+import com.android.server.art.model.Config;
import com.android.server.art.model.DeleteResult;
import com.android.server.art.model.OptimizationStatus;
import com.android.server.art.model.OptimizeParams;
@@ -46,8 +52,11 @@
import com.android.server.pm.pkg.PackageState;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
@@ -275,6 +284,80 @@
}
/**
+ * Runs batch optimization for the given reason.
+ *
+ * This is called by ART Service automatically during boot / background dexopt.
+ *
+ * The list of packages and options are determined by {@code reason}, and can be overridden by
+ * {@link #setOptimizePackagesCallback(Executor, OptimizePackagesCallback)}.
+ *
+ * The optimization is done in a thread pool. The number of packages being optimized
+ * simultaneously can be configured by system property {@code pm.dexopt.<reason>.concurrency}
+ * (e.g., {@code pm.dexopt.bg-dexopt.concurrency=4}), and the number of threads for each {@code
+ * dex2oat} invocation can be configured by system property {@code dalvik.vm.*dex2oat-threads}
+ * (e.g., {@code dalvik.vm.background-dex2oat-threads=4}). I.e., the maximum number of
+ * concurrent threads is the product of the two system properties. Note that the physical core
+ * usage is always bound by {@code dalvik.vm.*dex2oat-cpu-set} regardless of the number of
+ * threads.
+ *
+ * @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
+ * @param reason determines the default list of packages and options
+ * @param cancellationSignal provides the ability to cancel this operation
+ * @throws IllegalStateException if an internal error occurs, or the callback set by {@link
+ * #setOptimizePackagesCallback(Executor, OptimizePackagesCallback)} provides invalid
+ * params.
+ *
+ * @hide
+ */
+ @NonNull
+ public OptimizeResult optimizePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull @BatchOptimizeReason String reason,
+ @NonNull CancellationSignal cancellationSignal) {
+ List<String> defaultPackages =
+ Collections.unmodifiableList(getDefaultPackages(snapshot, reason));
+ OptimizeParams defaultOptimizeParams = new OptimizeParams.Builder(reason).build();
+ var builder = new BatchOptimizeParams.Builder(defaultPackages, defaultOptimizeParams);
+ Callback<OptimizePackagesCallback> callback =
+ mInjector.getConfig().getOptimizePackagesCallback();
+ if (callback != null) {
+ Utils.executeAndWait(callback.executor(), () -> {
+ callback.get().onOverrideBatchOptimizeParams(
+ snapshot, reason, defaultPackages, builder);
+ });
+ }
+ BatchOptimizeParams params = builder.build();
+ Utils.check(params.getOptimizeParams().getReason().equals(reason));
+
+ return mInjector.getDexOptHelper().dexopt(snapshot, params.getPackages(),
+ params.getOptimizeParams(), cancellationSignal,
+ Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason)));
+ }
+
+ /**
+ * Overrides the default params for {@link
+ * #optimizePackages(PackageManagerLocal.FilteredSnapshot, String). This method is thread-safe.
+ *
+ * This method gives users the opportunity to change the behavior of {@link
+ * #optimizePackages(PackageManagerLocal.FilteredSnapshot, String)}, which is called by ART
+ * Service automatically during boot / background dexopt.
+ *
+ * If this method is not called, the default list of packages and options determined by {@code
+ * reason} will be used.
+ */
+ public void setOptimizePackagesCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OptimizePackagesCallback callback) {
+ mInjector.getConfig().setOptimizePackagesCallback(executor, callback);
+ }
+
+ /**
+ * Clears the callback set by {@link #setOptimizePackagesCallback(Executor,
+ * OptimizePackagesCallback)}. This method is thread-safe.
+ */
+ public void clearOptimizePackagesCallback() {
+ mInjector.getConfig().clearOptimizePackagesCallback();
+ }
+
+ /**
* Notifies ART Service that a list of dex container files have been loaded.
*
* ART Service uses this information to:
@@ -298,6 +381,40 @@
snapshot, loadingPackageName, classLoaderContextByDexContainerFile);
}
+ @NonNull
+ private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull @BatchOptimizeReason String reason) {
+ var packages = new ArrayList<String>();
+ snapshot.forAllPackageStates((pkgState) -> {
+ if (Utils.canOptimizePackage(pkgState, mInjector.getAppHibernationManager())) {
+ packages.add(pkgState.getPackageName());
+ }
+ });
+ return packages;
+ }
+
+ public interface OptimizePackagesCallback {
+ /**
+ * Mutates {@code builder} to override the default params for {@link
+ * #optimizePackages(PackageManagerLocal.FilteredSnapshot, String). It must ignore unknown
+ * reasons because more reasons may be added in the future.
+ *
+ * If {@code builder.setPackages} is not called, {@code defaultPackages} will be used as the
+ * list of packages to optimize.
+ *
+ * If {@code builder.setOptimizeParams} is not called, the default params built from {@code
+ * new OptimizeParams.Builder(reason)} will to used as the params for optimizing each
+ * package.
+ *
+ * Changing the reason is not allowed. Doing so will result in {@link IllegalStateException}
+ * when {@link #optimizePackages(PackageManagerLocal.FilteredSnapshot, String,
+ * CancellationSignal)} is called.
+ */
+ void onOverrideBatchOptimizeParams(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull @BatchOptimizeReason String reason, @NonNull List<String> defaultPackages,
+ @NonNull BatchOptimizeParams.Builder builder);
+ }
+
/**
* Injector pattern for testing purpose.
*
@@ -307,26 +424,28 @@
public static class Injector {
@Nullable private final Context mContext;
@Nullable private final PackageManagerLocal mPackageManagerLocal;
+ @Nullable private final Config mConfig;
Injector(@Nullable Context context) {
mContext = context;
- mPackageManagerLocal = LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ if (context != null) {
+ // We only need them on Android U and above, where a context is passed.
+ mPackageManagerLocal = LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ mConfig = new Config();
+ } else {
+ mPackageManagerLocal = null;
+ mConfig = null;
+ }
}
@NonNull
public Context getContext() {
- if (mContext == null) {
- throw new IllegalStateException("Context is null");
- }
- return mContext;
+ return Objects.requireNonNull(mContext);
}
@NonNull
public PackageManagerLocal getPackageManagerLocal() {
- if (mPackageManagerLocal == null) {
- throw new IllegalStateException("PackageManagerLocal is null");
- }
- return mPackageManagerLocal;
+ return Objects.requireNonNull(mPackageManagerLocal);
}
@NonNull
@@ -338,5 +457,15 @@
public DexOptHelper getDexOptHelper() {
return new DexOptHelper(getContext());
}
+
+ @NonNull
+ public Config getConfig() {
+ return mConfig;
+ }
+
+ @NonNull
+ public AppHibernationManager getAppHibernationManager() {
+ return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
+ }
}
}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index 0e0d80c..b50ad3e 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -27,6 +27,7 @@
import android.os.CancellationSignal;
import android.os.Process;
+import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.BasicShellCommandHandler;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DeleteResult;
@@ -53,7 +54,9 @@
private final PackageManagerLocal mPackageManagerLocal;
private final DexUseManager mDexUseManager = DexUseManager.getInstance();
- private static Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
+ @GuardedBy("sCancellationSignalMap")
+ @NonNull
+ private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
public ArtShellCommand(
ArtManagerLocal artManagerLocal, PackageManagerLocal packageManagerLocal) {
@@ -105,40 +108,21 @@
}
}
- String jobId = UUID.randomUUID().toString();
- var signal = new CancellationSignal();
- pw.printf("Job ID: %s\n", jobId);
- pw.flush();
-
- synchronized (sCancellationSignalMap) {
- sCancellationSignalMap.put(jobId, signal);
- }
-
OptimizeResult result;
- try {
- result = mArtManagerLocal.optimizePackage(
- snapshot, getNextArgRequired(), paramsBuilder.build(), signal);
- } finally {
- synchronized (sCancellationSignalMap) {
- sCancellationSignalMap.remove(jobId);
- }
+ try (var signal = new WithCancellationSignal(pw)) {
+ result = mArtManagerLocal.optimizePackage(snapshot, getNextArgRequired(),
+ paramsBuilder.build(), signal.get());
}
-
- pw.println(optimizeStatusToString(result.getFinalStatus()));
- for (PackageOptimizeResult packageResult : result.getPackageOptimizeResults()) {
- pw.printf("[%s]\n", packageResult.getPackageName());
- for (DexContainerFileOptimizeResult fileResult :
- packageResult.getDexContainerFileOptimizeResults()) {
- pw.printf("dexContainerFile = %s, isPrimaryAbi = %b, abi = %s, "
- + "compilerFilter = %s, status = %s, "
- + "dex2oatWallTimeMillis = %d, dex2oatCpuTimeMillis = %d\n",
- fileResult.getDexContainerFile(), fileResult.isPrimaryAbi(),
- fileResult.getAbi(), fileResult.getActualCompilerFilter(),
- optimizeStatusToString(fileResult.getStatus()),
- fileResult.getDex2oatWallTimeMillis(),
- fileResult.getDex2oatCpuTimeMillis());
- }
+ printOptimizeResult(pw, result);
+ return 0;
+ }
+ case "optimize-packages": {
+ OptimizeResult result;
+ try (var signal = new WithCancellationSignal(pw)) {
+ result = mArtManagerLocal.optimizePackages(
+ snapshot, getNextArgRequired(), signal.get());
}
+ printOptimizeResult(pw, result);
return 0;
}
case "cancel": {
@@ -237,6 +221,10 @@
pw.println(" -f Force compilation.");
pw.println(" --secondary-dex Only compile secondary dex.");
pw.println(" --include-dependencies Include dependencies.");
+ pw.println(" optimize-packages REASON");
+ pw.println(" Run batch optimization for the given reason.");
+ pw.println(" The command prints a job ID, which can be used to cancel the job using the"
+ + "'cancel' command.");
pw.println(" cancel JOB_ID");
pw.println(" Cancel a job.");
pw.println(" dex-use-notify PACKAGE_NAME DEX_PATH CLASS_LOADER_CONTEXT");
@@ -277,4 +265,48 @@
}
throw new IllegalArgumentException("Unknown optimize status " + status);
}
+
+ private void printOptimizeResult(@NonNull PrintWriter pw, @NonNull OptimizeResult result) {
+ pw.println(optimizeStatusToString(result.getFinalStatus()));
+ for (PackageOptimizeResult packageResult : result.getPackageOptimizeResults()) {
+ pw.printf("[%s]\n", packageResult.getPackageName());
+ for (DexContainerFileOptimizeResult fileResult :
+ packageResult.getDexContainerFileOptimizeResults()) {
+ pw.printf("dexContainerFile = %s, isPrimaryAbi = %b, abi = %s, "
+ + "compilerFilter = %s, status = %s, "
+ + "dex2oatWallTimeMillis = %d, dex2oatCpuTimeMillis = %d\n",
+ fileResult.getDexContainerFile(), fileResult.isPrimaryAbi(),
+ fileResult.getAbi(), fileResult.getActualCompilerFilter(),
+ optimizeStatusToString(fileResult.getStatus()),
+ fileResult.getDex2oatWallTimeMillis(),
+ fileResult.getDex2oatCpuTimeMillis());
+ }
+ }
+ }
+
+ private static class WithCancellationSignal implements AutoCloseable {
+ @NonNull private final CancellationSignal mSignal = new CancellationSignal();
+ @NonNull private final String mJobId;
+
+ public WithCancellationSignal(@NonNull PrintWriter pw) {
+ mJobId = UUID.randomUUID().toString();
+ pw.printf("Job ID: %s\n", mJobId);
+ pw.flush();
+
+ synchronized (sCancellationSignalMap) {
+ sCancellationSignalMap.put(mJobId, mSignal);
+ }
+ }
+
+ @NonNull
+ public CancellationSignal get() {
+ return mSignal;
+ }
+
+ public void close() {
+ synchronized (sCancellationSignalMap) {
+ sCancellationSignalMap.remove(mJobId);
+ }
+ }
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/DexOptHelper.java b/libartservice/service/java/com/android/server/art/DexOptHelper.java
index b30e25d..43be7d3 100644
--- a/libartservice/service/java/com/android/server/art/DexOptHelper.java
+++ b/libartservice/service/java/com/android/server/art/DexOptHelper.java
@@ -42,13 +42,14 @@
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -146,7 +147,7 @@
AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
- if (!canOptimizePackage(pkgState, pkg)) {
+ if (!canOptimizePackage(pkgState)) {
return createResult.get();
}
@@ -178,20 +179,8 @@
return createResult.get();
}
- private boolean canOptimizePackage(
- @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) {
- if (!pkg.getSplits().get(0).isHasCode()) {
- return false;
- }
-
- // We do not dexopt unused packages.
- AppHibernationManager ahm = mInjector.getAppHibernationManager();
- if (ahm.isHibernatingGlobally(pkgState.getPackageName())
- && ahm.isOatArtifactDeletionEnabled()) {
- return false;
- }
-
- return true;
+ private boolean canOptimizePackage(@NonNull PackageState pkgState) {
+ return Utils.canOptimizePackage(pkgState, mInjector.getAppHibernationManager());
}
@NonNull
@@ -213,9 +202,9 @@
for (String packageName : packageNames) {
PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
- AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+ Utils.getPackageOrThrow(pkgState);
pkgStates.put(packageName, pkgState);
- if (includeDependencies && canOptimizePackage(pkgState, pkg)) {
+ if (includeDependencies && canOptimizePackage(pkgState)) {
for (SharedLibrary library : pkgState.getUsesLibraries()) {
maybeEnqueue.accept(library);
}
@@ -226,8 +215,7 @@
while ((library = queue.poll()) != null) {
String packageName = library.getPackageName();
PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
- AndroidPackage pkg = pkgState.getAndroidPackage();
- if (pkg != null && canOptimizePackage(pkgState, pkg)) {
+ if (canOptimizePackage(pkgState)) {
pkgStates.put(packageName, pkgState);
// Note that `library.getDependencies()` is different from
@@ -274,12 +262,12 @@
@NonNull
public AppHibernationManager getAppHibernationManager() {
- return mContext.getSystemService(AppHibernationManager.class);
+ return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
}
@NonNull
public PowerManager getPowerManager() {
- return mContext.getSystemService(PowerManager.class);
+ return Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
}
}
}
diff --git a/libartservice/service/java/com/android/server/art/DexUseManager.java b/libartservice/service/java/com/android/server/art/DexUseManager.java
index e51c3bf..e3e8134 100644
--- a/libartservice/service/java/com/android/server/art/DexUseManager.java
+++ b/libartservice/service/java/com/android/server/art/DexUseManager.java
@@ -220,7 +220,7 @@
// "android" comes from `SystemServerDexLoadReporter`. ART Services doesn't need to handle
// this case because it doesn't compile system server and system server isn't allowed to
// load artifacts produced by ART Services.
- if (loadingPackageName.equals("android")) {
+ if (loadingPackageName.equals(Utils.PLATFORM_PACKAGE_NAME)) {
return;
}
diff --git a/libartservice/service/java/com/android/server/art/ReasonMapping.java b/libartservice/service/java/com/android/server/art/ReasonMapping.java
index 08140ad..98c82d8 100644
--- a/libartservice/service/java/com/android/server/art/ReasonMapping.java
+++ b/libartservice/service/java/com/android/server/art/ReasonMapping.java
@@ -19,15 +19,19 @@
import static com.android.server.art.model.ArtFlags.PriorityClassApi;
import android.annotation.NonNull;
+import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.os.SystemProperties;
import android.text.TextUtils;
import com.android.server.art.model.ArtFlags;
+import com.android.server.pm.PackageManagerLocal;
import dalvik.system.DexFile;
import java.util.Set;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Maps a compilation reason to a compiler filter and a priority class.
@@ -65,6 +69,22 @@
REASON_INSTALL_BULK_DOWNGRADED, REASON_INSTALL_BULK_SECONDARY_DOWNGRADED);
/**
+ * Reasons for
+ * {@link ArtManagerLocal#optimizePackages(PackageManagerLocal.FilteredSnapshot, String)}.
+ *
+ * @hide
+ */
+ // clang-format off
+ @StringDef(prefix = "REASON_", value = {
+ REASON_FIRST_BOOT,
+ REASON_BOOT_AFTER_OTA,
+ REASON_BG_DEXOPT,
+ })
+ // clang-format on
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BatchOptimizeReason {}
+
+ /**
* Loads the compiler filter from the system property for the given reason and checks for
* validity.
*
@@ -136,4 +156,15 @@
throw new IllegalArgumentException("No priority class for reason '" + reason + "'");
}
}
+
+ /**
+ * Loads the concurrency from the system property, for batch optimization ({@link
+ * ArtManagerLocal#optimizePackages(PackageManagerLocal.FilteredSnapshot, String)}), or 1 if the
+ * system property is not found or cannot be parsed.
+ *
+ * @hide
+ */
+ public static int getConcurrencyForReason(@NonNull @BatchOptimizeReason String reason) {
+ return SystemProperties.getInt("pm.dexopt." + reason + ".concurrency", 1 /* def */);
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index e4060c1..19adc3f 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.apphibernation.AppHibernationManager;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -40,12 +41,15 @@
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;
/** @hide */
public final class Utils {
+ public static final String PLATFORM_PACKAGE_NAME = "android";
+
private Utils() {}
/**
@@ -211,6 +215,10 @@
return str;
}
+ public static void executeAndWait(@NonNull Executor executor, @NonNull Runnable runnable) {
+ getFuture(execute(executor, Executors.callable(runnable)));
+ }
+
public static <T> Future<T> execute(@NonNull Executor executor, @NonNull Callable<T> callable) {
var future = new FutureTask<T>(callable);
executor.execute(future);
@@ -231,6 +239,41 @@
}
}
+ /**
+ * Returns true if the given package is optimizable.
+ *
+ * @param appHibernationManager the {@link AppHibernationManager} instance for checking
+ * hibernation status, or null to skip the check
+ */
+ public static boolean canOptimizePackage(
+ @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) {
+ // An APEX has a uid of -1.
+ // TODO(b/256637152): Consider using `isApex` instead.
+ if (pkgState.getAppId() <= 0) {
+ return false;
+ }
+
+ // "android" is a special package that represents the platform, not an app.
+ if (pkgState.getPackageName().equals(Utils.PLATFORM_PACKAGE_NAME)) {
+ return false;
+ }
+
+ AndroidPackage pkg = pkgState.getAndroidPackage();
+ if (pkg == null || !pkg.getSplits().get(0).isHasCode()) {
+ return false;
+ }
+
+ // We do not dexopt unused packages.
+ // If `appHibernationManager` is null, the caller's intention is to skip the check.
+ if (appHibernationManager != null
+ && appHibernationManager.isHibernatingGlobally(pkgState.getPackageName())
+ && appHibernationManager.isOatArtifactDeletionEnabled()) {
+ return false;
+ }
+
+ return true;
+ }
+
@AutoValue
public abstract static class Abi {
static @NonNull Abi create(
diff --git a/libartservice/service/java/com/android/server/art/model/BatchOptimizeParams.java b/libartservice/service/java/com/android/server/art/model/BatchOptimizeParams.java
new file mode 100644
index 0000000..f0025e4
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/BatchOptimizeParams.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 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.art.model;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** @hide */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+public abstract class BatchOptimizeParams {
+ public static final class Builder {
+ private @NonNull List<String> mPackageNames; // This is assumed immutable.
+ private @NonNull OptimizeParams mOptimizeParams;
+
+ /** @hide */
+ public Builder(@NonNull List<String> defaultPackages,
+ @NonNull OptimizeParams defaultOptimizeParams) {
+ mPackageNames = defaultPackages; // The argument is assumed immutable.
+ mOptimizeParams = defaultOptimizeParams;
+ }
+
+ /**
+ * Sets the list of packages to optimize. The optimization will be scheduled in the given
+ * order.
+ *
+ * If not called, the default list will be used.
+ */
+ @NonNull
+ public Builder setPackages(@NonNull List<String> packageNames) {
+ mPackageNames = Collections.unmodifiableList(new ArrayList<>(packageNames));
+ return this;
+ }
+
+ /**
+ * Sets the params for optimizing each package.
+ *
+ * If not called, the default params built from {@link OptimizeParams#Builder(String)} will
+ * be used.
+ */
+ @NonNull
+ public Builder setOptimizeParams(@NonNull OptimizeParams optimizeParams) {
+ mOptimizeParams = optimizeParams;
+ return this;
+ }
+
+ /** Returns the built object. */
+ @NonNull
+ public BatchOptimizeParams build() {
+ return new AutoValue_BatchOptimizeParams(mPackageNames, mOptimizeParams);
+ }
+ }
+
+ /** @hide */
+ protected BatchOptimizeParams() {}
+
+ /** The ordered list of packages to optimize. */
+ public abstract @NonNull List<String> getPackages();
+
+ /** The params for optimizing each package. */
+ public abstract @NonNull OptimizeParams getOptimizeParams();
+}
diff --git a/libartservice/service/java/com/android/server/art/model/Config.java b/libartservice/service/java/com/android/server/art/model/Config.java
new file mode 100644
index 0000000..3082685
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/Config.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.art.model;
+
+import static com.android.server.art.ArtManagerLocal.OptimizePackagesCallback;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.art.ArtManagerLocal;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A class that stores the configurations set by the consumer of ART Service at runtime. This class
+ * is thread-safe.
+ *
+ * @hide
+ */
+public class Config {
+ /** @see ArtManagerLocal#setOptimizePackagesCallback(Executor, OptimizePackagesCallback) */
+ @GuardedBy("this")
+ @Nullable
+ private Callback<OptimizePackagesCallback> mOptimizePackagesCallback = null;
+
+ public synchronized void setOptimizePackagesCallback(
+ @NonNull Executor executor, @NonNull OptimizePackagesCallback callback) {
+ mOptimizePackagesCallback = Callback.<OptimizePackagesCallback>create(callback, executor);
+ }
+
+ public synchronized void clearOptimizePackagesCallback() {
+ mOptimizePackagesCallback = null;
+ }
+
+ @Nullable
+ public synchronized Callback<OptimizePackagesCallback> getOptimizePackagesCallback() {
+ return mOptimizePackagesCallback;
+ }
+
+ @AutoValue
+ public static abstract class Callback<T> {
+ public abstract @NonNull T get();
+ public abstract @NonNull Executor executor();
+ static <T> @NonNull Callback<T> create(@NonNull T callback, @NonNull Executor executor) {
+ return new AutoValue_Config_Callback<T>(callback, executor);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizeParams.java b/libartservice/service/java/com/android/server/art/model/OptimizeParams.java
index 6f40546..ae8c984 100644
--- a/libartservice/service/java/com/android/server/art/model/OptimizeParams.java
+++ b/libartservice/service/java/com/android/server/art/model/OptimizeParams.java
@@ -22,11 +22,13 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import com.android.internal.annotations.Immutable;
import com.android.server.art.ReasonMapping;
import com.android.server.art.Utils;
/** @hide */
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
public class OptimizeParams {
public static final class Builder {
private OptimizeParams mParams = new OptimizeParams();
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index e9a2beb..f0aa7e5 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -21,9 +21,11 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@@ -32,12 +34,14 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.apphibernation.AppHibernationManager;
import android.os.CancellationSignal;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import androidx.test.filters.SmallTest;
+import com.android.server.art.model.Config;
import com.android.server.art.model.DeleteResult;
import com.android.server.art.model.OptimizationStatus;
import com.android.server.art.model.OptimizeParams;
@@ -58,11 +62,15 @@
import org.mockito.Mock;
import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
@SmallTest
@RunWith(Parameterized.class)
public class ArtManagerLocalTest {
private static final String PKG_NAME = "com.example.foo";
+ private static final String PKG_NAME_SYS_UI = "com.android.systemui";
+ private static final String PKG_NAME_HIBERNATING = "com.example.hibernating";
@Rule
public StaticMockitoRule mockitoRule =
@@ -73,8 +81,10 @@
@Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
@Mock private IArtd mArtd;
@Mock private DexOptHelper mDexOptHelper;
+ @Mock private AppHibernationManager mAppHibernationManager;
private PackageState mPkgState;
private AndroidPackage mPkg;
+ private Config mConfig;
// True if the primary dex'es are in a readonly partition.
@Parameter(0) public boolean mIsInReadonlyPartition;
@@ -88,14 +98,23 @@
@Before
public void setUp() throws Exception {
+ mConfig = new Config();
+
// Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs.
// These are the default test setups. They may or may not be used depending on the code path
// that each test case examines.
lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
lenient().when(mInjector.getArtd()).thenReturn(mArtd);
lenient().when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper);
+ lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+ lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAppHibernationManager);
lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile");
+ lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile");
+ lenient().when(SystemProperties.get(eq("pm.dexopt.first-boot"))).thenReturn("verify");
+ lenient()
+ .when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
+ .thenReturn(3);
// No ISA translation.
lenient()
@@ -106,14 +125,28 @@
lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
- mPkgState = createPackageState();
+ lenient().when(mAppHibernationManager.isHibernatingGlobally(any())).thenReturn(false);
+ lenient().when(mAppHibernationManager.isOatArtifactDeletionEnabled()).thenReturn(true);
+
+ lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
+ List<PackageState> pkgStates = createPackageStates();
+ for (PackageState pkgState : pkgStates) {
+ lenient()
+ .when(mSnapshot.getPackageState(pkgState.getPackageName()))
+ .thenReturn(pkgState);
+ }
+ lenient()
+ .doAnswer(invocation -> {
+ var consumer = invocation.<Consumer<PackageState>>getArgument(0);
+ for (PackageState pkgState : pkgStates) {
+ consumer.accept(pkgState);
+ }
+ return null;
+ })
+ .when(mSnapshot)
+ .forAllPackageStates(any());
+ mPkgState = mSnapshot.getPackageState(PKG_NAME);
mPkg = mPkgState.getAndroidPackage();
- lenient()
- .when(mPackageManagerLocal.withFilteredSnapshot())
- .thenReturn(mSnapshot);
- lenient()
- .when(mSnapshot.getPackageState(mPkgState.getPackageName()))
- .thenReturn(mPkgState);
mArtManagerLocal = new ArtManagerLocal(mInjector);
}
@@ -122,8 +155,7 @@
public void testDeleteOptimizedArtifacts() throws Exception {
when(mArtd.deleteArtifacts(any())).thenReturn(1l);
- DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(
- mSnapshot, PKG_NAME);
+ DeleteResult result = mArtManagerLocal.deleteOptimizedArtifacts(mSnapshot, PKG_NAME);
assertThat(result.getFreedBytes()).isEqualTo(4);
verify(mArtd).deleteArtifacts(argThat(artifactsPath
@@ -203,8 +235,7 @@
createGetOptimizationStatusResult(
"extract", "compilation-reason-3", "location-debug-string-3"));
- OptimizationStatus result =
- mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
+ OptimizationStatus result = mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
List<DexContainerFileOptimizationStatus> statuses =
result.getDexContainerFileOptimizationStatuses();
@@ -258,8 +289,7 @@
when(mArtd.getOptimizationStatus(any(), any(), any()))
.thenThrow(new ServiceSpecificException(1 /* errorCode */, "some error message"));
- OptimizationStatus result =
- mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
+ OptimizationStatus result = mArtManagerLocal.getOptimizationStatus(mSnapshot, PKG_NAME);
List<DexContainerFileOptimizationStatus> statuses =
result.getDexContainerFileOptimizationStatuses();
@@ -282,47 +312,158 @@
same(cancellationSignal), any()))
.thenReturn(result);
- assertThat(mArtManagerLocal.optimizePackage(mSnapshot,
- PKG_NAME, params, cancellationSignal))
+ assertThat(
+ mArtManagerLocal.optimizePackage(mSnapshot, PKG_NAME, params, cancellationSignal))
.isSameInstanceAs(result);
}
- private AndroidPackage createPackage() {
+ @Test
+ public void testOptimizePackages() throws Exception {
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ // It should use the default package list and params.
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME, PKG_NAME_SYS_UI)), any(),
+ same(cancellationSignal), any()))
+ .thenReturn(result);
+
+ assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
+ .isSameInstanceAs(result);
+ }
+
+ @Test
+ public void testOptimizePackagesOverride() throws Exception {
+ var params = new OptimizeParams.Builder("bg-dexopt").build();
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
+ (snapshot, reason, defaultPackages, builder) -> {
+ assertThat(reason).isEqualTo("bg-dexopt");
+ assertThat(defaultPackages).containsExactly(PKG_NAME, PKG_NAME_SYS_UI);
+ builder.setPackages(List.of(PKG_NAME)).setOptimizeParams(params);
+ });
+
+ // It should use the overridden package list and params.
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
+ same(cancellationSignal), any()))
+ .thenReturn(result);
+
+ assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
+ .isSameInstanceAs(result);
+ }
+
+ @Test
+ public void testOptimizePackagesOverrideCleared() throws Exception {
+ var params = new OptimizeParams.Builder("bg-dexopt").build();
+ var result = mock(OptimizeResult.class);
+ var cancellationSignal = new CancellationSignal();
+
+ mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
+ (snapshot, reason, defaultPackages, builder) -> {
+ builder.setPackages(List.of(PKG_NAME)).setOptimizeParams(params);
+ });
+ mArtManagerLocal.clearOptimizePackagesCallback();
+
+ // It should use the default package list and params.
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME, PKG_NAME_SYS_UI)),
+ not(same(params)), same(cancellationSignal), any()))
+ .thenReturn(result);
+
+ assertThat(mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal))
+ .isSameInstanceAs(result);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOptimizePackagesOverrideReasonChanged() throws Exception {
+ var params = new OptimizeParams.Builder("first-boot").build();
+ var cancellationSignal = new CancellationSignal();
+
+ mArtManagerLocal.setOptimizePackagesCallback(Executors.newSingleThreadExecutor(),
+ (snapshot, reason, defaultPackages, builder) -> {
+ builder.setOptimizeParams(params);
+ });
+
+ mArtManagerLocal.optimizePackages(mSnapshot, "bg-dexopt", cancellationSignal);
+ }
+
+ private AndroidPackage createPackage(boolean multiSplit) {
AndroidPackage pkg = mock(AndroidPackage.class);
var baseSplit = mock(AndroidPackageSplit.class);
lenient().when(baseSplit.getPath()).thenReturn("/data/app/foo/base.apk");
lenient().when(baseSplit.isHasCode()).thenReturn(true);
- // split_0 has code while split_1 doesn't.
- var split0 = mock(AndroidPackageSplit.class);
- lenient().when(split0.getName()).thenReturn("split_0");
- lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
- lenient().when(split0.isHasCode()).thenReturn(true);
- var split1 = mock(AndroidPackageSplit.class);
- lenient().when(split1.getName()).thenReturn("split_1");
- lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
- lenient().when(split1.isHasCode()).thenReturn(false);
+ if (multiSplit) {
+ // split_0 has code while split_1 doesn't.
+ var split0 = mock(AndroidPackageSplit.class);
+ lenient().when(split0.getName()).thenReturn("split_0");
+ lenient().when(split0.getPath()).thenReturn("/data/app/foo/split_0.apk");
+ lenient().when(split0.isHasCode()).thenReturn(true);
+ var split1 = mock(AndroidPackageSplit.class);
+ lenient().when(split1.getName()).thenReturn("split_1");
+ lenient().when(split1.getPath()).thenReturn("/data/app/foo/split_1.apk");
+ lenient().when(split1.isHasCode()).thenReturn(false);
- var splits = List.of(baseSplit, split0, split1);
- lenient().when(pkg.getSplits()).thenReturn(splits);
+ lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0, split1));
+ } else {
+ lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
+ }
+
return pkg;
}
- private PackageState createPackageState() {
+ private PackageState createPackageState(
+ String packageName, int appId, boolean hasPackage, boolean multiSplit) {
PackageState pkgState = mock(PackageState.class);
- lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
+ lenient().when(pkgState.getPackageName()).thenReturn(packageName);
lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
lenient().when(pkgState.isSystem()).thenReturn(mIsInReadonlyPartition);
lenient().when(pkgState.isUpdatedSystemApp()).thenReturn(false);
- AndroidPackage pkg = createPackage();
- lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+ lenient().when(pkgState.getAppId()).thenReturn(appId);
+
+ if (hasPackage) {
+ AndroidPackage pkg = createPackage(multiSplit);
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+ } else {
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(null);
+ }
return pkgState;
}
+ private List<PackageState> createPackageStates() {
+ PackageState pkgState = createPackageState(
+ PKG_NAME, 10001 /* appId */, true /* hasPackage */, true /* multiSplit */);
+
+ PackageState sysUiPkgState = createPackageState(
+ PKG_NAME_SYS_UI, 1234 /* appId */, true /* hasPackage */, false /* multiSplit */);
+
+ // This should not be optimized because it's hibernating.
+ PackageState pkgHibernatingState = createPackageState(PKG_NAME_HIBERNATING,
+ 10002 /* appId */, true /* hasPackage */, false /* multiSplit */);
+ lenient()
+ .when(mAppHibernationManager.isHibernatingGlobally(PKG_NAME_HIBERNATING))
+ .thenReturn(true);
+
+ // This should not be optimized because it does't have AndroidPackage.
+ PackageState nullPkgState = createPackageState("com.example.null", 10003 /* appId */,
+ false /* hasPackage */, false /* multiSplit */);
+
+ // This should not be optimized because it has a negative app id.
+ PackageState apexPkgState = createPackageState(
+ "com.android.art", -1 /* appId */, true /* hasPackage */, false /* multiSplit */);
+
+ // This should not be optimized because it's "android".
+ PackageState platformPkgState = createPackageState(Utils.PLATFORM_PACKAGE_NAME,
+ 1000 /* appId */, true /* hasPackage */, false /* multiSplit */);
+
+ return List.of(pkgState, sysUiPkgState, pkgHibernatingState, nullPkgState, apexPkgState,
+ platformPkgState);
+ }
+
private GetOptimizationStatusResult createGetOptimizationStatusResult(
String compilerFilter, String compilationReason, String locationDebugString) {
var getOptimizationStatusResult = new GetOptimizationStatusResult();
diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
index f83d1bc..7fa2dfc 100644
--- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -211,7 +211,7 @@
private void verifyPrimaryDexMultipleEntries(boolean saveAndLoad) throws Exception {
// These should be ignored.
- mDexUseManager.addDexUse(mSnapshot, "android", Map.of(BASE_APK, "CLC"));
+ mDexUseManager.addDexUse(mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(BASE_APK, "CLC"));
mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME,
Map.of("/data/app/" + OWNING_PKG_NAME + "/non-existing.apk", "CLC"));
@@ -333,7 +333,8 @@
private void verifySecondaryDexMultipleEntries(boolean saveAndLoad) throws Exception {
// These should be ignored.
- mDexUseManager.addDexUse(mSnapshot, "android", Map.of(mCeDir + "/foo.apk", "CLC"));
+ mDexUseManager.addDexUse(
+ mSnapshot, Utils.PLATFORM_PACKAGE_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
mDexUseManager.addDexUse(
mSnapshot, OWNING_PKG_NAME, Map.of("/some/non-existing.apk", "CLC"));
diff --git a/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java
index 5933643..55fd0b4 100644
--- a/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ReasonMappingTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
import android.os.SystemProperties;
@@ -76,4 +78,11 @@
public void testGetPriorityClassForReasonInvalidReason() throws Exception {
ReasonMapping.getPriorityClassForReason("foo");
}
+
+ @Test
+ public void testGetConcurrencyForReason() {
+ when(SystemProperties.getInt(eq("pm.dexopt.bg-dexopt.concurrency"), anyInt()))
+ .thenReturn(3);
+ assertThat(ReasonMapping.getConcurrencyForReason("bg-dexopt")).isEqualTo(3);
+ }
}
diff --git a/libartservice/service/javatests/com/android/server/art/UtilsTest.java b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
index 79d1fd3..93e927b 100644
--- a/libartservice/service/javatests/com/android/server/art/UtilsTest.java
+++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
@@ -38,7 +38,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -172,4 +175,25 @@
public void testCheckFailed() throws Exception {
Utils.check(false);
}
+
+ @Test
+ public void testExecuteAndWait() {
+ Executor executor = Executors.newSingleThreadExecutor();
+ List<String> results = new ArrayList<>();
+ Utils.executeAndWait(executor, () -> {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ results.add("foo");
+ });
+ assertThat(results).containsExactly("foo");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExecuteAndWaitPropagatesException() {
+ Executor executor = Executors.newSingleThreadExecutor();
+ Utils.executeAndWait(executor, () -> { throw new IllegalArgumentException(); });
+ }
}