Optionally use ART Service for optimizing packages.

It is only used when the system property dalvik.vm.useartservice is
true and the requested options are supported.

Test: adb root
      adb shell cmd package force-dex-opt com.google.android.dialer
  with dalvik.vm.useartservice=true and
  DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE added to the
  flags in DexOptHelper.forceDexOpt. Check that artd calls dex2oat and
  that /data/dalvik-cache/x86_64/*@classes.dex are created and used
  with:
      adb shell am start -S -W -c android.intent.category.LAUNCHER \
        -a android.intent.action.MAIN com.google.android.dialer
      adb shell cat '/proc/`pidof com.google.android.dialer`/maps' \
        | grep classes.dex
Bug: 251903639
Change-Id: Iea2fd2d1cb5d77e725f51c00211d7df43826700a
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 3f04264..c4f6836 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
+import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.ApexManager.ActiveApexInfo;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
@@ -34,6 +35,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -56,9 +58,16 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.PackageDexOptimizer.DexOptResult;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 
 import dalvik.system.DexFile;
@@ -72,11 +81,15 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
-final class DexOptHelper {
+/**
+ * Helper class for dex optimization operations in PackageManagerService.
+ */
+public final class DexOptHelper {
     private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
 
     private final PackageManagerService mPm;
@@ -405,11 +418,12 @@
      * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
      * {@link PackageDexOptimizer#DEX_OPT_FAILED}
      */
-    @PackageDexOptimizer.DexOptResult
+    @DexOptResult
     /* package */ int performDexOptWithStatus(DexoptOptions options) {
         return performDexOptTraced(options);
     }
 
+    @DexOptResult
     private int performDexOptTraced(DexoptOptions options) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
         try {
@@ -421,7 +435,13 @@
 
     // 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.
+    @DexOptResult
     private int performDexOptInternal(DexoptOptions options) {
+        Optional<Integer> artSrvRes = performDexOptWithArtService(options);
+        if (artSrvRes.isPresent()) {
+            return artSrvRes.get();
+        }
+
         AndroidPackage p;
         PackageSetting pkgSetting;
         synchronized (mPm.mLock) {
@@ -446,8 +466,74 @@
         }
     }
 
-    private int performDexOptInternalWithDependenciesLI(AndroidPackage p,
-            @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
+    /**
+     * Performs dexopt on the given package using ART Service.
+     *
+     * @return a {@link DexOptResult}, or empty if the request isn't supported so that it is
+     *     necessary to fall back to the legacy code paths.
+     */
+    private Optional<Integer> performDexOptWithArtService(DexoptOptions options) {
+        ArtManagerLocal artManager = getArtManagerLocal();
+        if (artManager == null) {
+            return Optional.empty();
+        }
+
+        try (PackageManagerLocal.FilteredSnapshot snapshot =
+                        getPackageManagerLocal().withFilteredSnapshot()) {
+            PackageState ops = snapshot.getPackageState(options.getPackageName());
+            if (ops == null) {
+                return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+            }
+            AndroidPackage oap = ops.getAndroidPackage();
+            if (oap == null) {
+                return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+            }
+            if (oap.isApex()) {
+                return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED);
+            }
+
+            // TODO(b/245301593): Delete the conditional when ART Service supports
+            // FLAG_SHOULD_INCLUDE_DEPENDENCIES and we can just set it unconditionally.
+            /*@OptimizeFlags*/ int extraFlags = ops.getUsesLibraries().isEmpty()
+                    ? 0
+                    : ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+
+            OptimizeParams params = options.convertToOptimizeParams(extraFlags);
+            if (params == null) {
+                return Optional.empty();
+            }
+
+            // TODO(b/251903639): Either remove controlDexOptBlocking, or don't ignore it here.
+            OptimizeResult result;
+            try {
+                result = artManager.optimizePackage(snapshot, options.getPackageName(), params);
+            } catch (UnsupportedOperationException e) {
+                reportArtManagerFallback(options.getPackageName(), e.toString());
+                return Optional.empty();
+            }
+
+            // TODO(b/251903639): Move this to ArtManagerLocal.addOptimizePackageDoneCallback when
+            // it is implemented.
+            for (OptimizeResult.PackageOptimizeResult pkgRes : result.getPackageOptimizeResults()) {
+                PackageState ps = snapshot.getPackageState(pkgRes.getPackageName());
+                AndroidPackage ap = ps != null ? ps.getAndroidPackage() : null;
+                if (ap != null) {
+                    CompilerStats.PackageStats stats = mPm.getOrCreateCompilerPackageStats(ap);
+                    for (OptimizeResult.DexContainerFileOptimizeResult dexRes :
+                            pkgRes.getDexContainerFileOptimizeResults()) {
+                        stats.setCompileTime(
+                                dexRes.getDexContainerFile(), dexRes.getDex2oatWallTimeMillis());
+                    }
+                }
+            }
+
+            return Optional.of(convertToDexOptResult(result));
+        }
+    }
+
+    @DexOptResult
+    private int performDexOptInternalWithDependenciesLI(
+            AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
         // System server gets a special path.
         if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
             return mPm.getDexManager().dexoptSystemServer(options);
@@ -514,10 +600,20 @@
 
         // 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, packageState,
-                new DexoptOptions(packageName, REASON_CMDLINE,
-                        getDefaultCompilerFilter(), null /* splitName */,
-                        DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+        DexoptOptions options = new DexoptOptions(packageName, REASON_CMDLINE,
+                getDefaultCompilerFilter(), null /* splitName */,
+                DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
+
+        // performDexOptWithArtService ignores the snapshot and takes its own, so it can race with
+        // the package checks above, but at worst the effect is only a bit less friendly error
+        // below.
+        Optional<Integer> artSrvRes = performDexOptWithArtService(options);
+        int res;
+        if (artSrvRes.isPresent()) {
+            res = artSrvRes.get();
+        } else {
+            res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
+        }
 
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -800,4 +896,59 @@
         }
         return false;
     }
+
+    private @NonNull PackageManagerLocal getPackageManagerLocal() {
+        try {
+            return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
+        } catch (ManagerNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Called whenever we need to fall back from ART Service to the legacy dexopt code.
+     */
+    public static void reportArtManagerFallback(String packageName, String reason) {
+        // STOPSHIP(b/251903639): Minimize these calls to avoid platform getting shipped with code
+        // paths that will always bypass ART Service.
+        Slog.i(TAG, "Falling back to old PackageManager dexopt for " + packageName + ": " + reason);
+    }
+
+    /**
+     * Returns {@link ArtManagerLocal} if one is found and should be used for package optimization.
+     */
+    private @Nullable ArtManagerLocal getArtManagerLocal() {
+        if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) {
+            return null;
+        }
+        try {
+            return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class);
+        } catch (ManagerNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Converts an ART Service {@link OptimizeResult} to {@link DexOptResult}.
+     *
+     * For interfacing {@link ArtManagerLocal} with legacy dex optimization code in PackageManager.
+     */
+    @DexOptResult
+    private static int convertToDexOptResult(OptimizeResult result) {
+        /*@OptimizeStatus*/ int status = result.getFinalStatus();
+        switch (status) {
+            case OptimizeResult.OPTIMIZE_SKIPPED:
+                return PackageDexOptimizer.DEX_OPT_SKIPPED;
+            case OptimizeResult.OPTIMIZE_FAILED:
+                return PackageDexOptimizer.DEX_OPT_FAILED;
+            case OptimizeResult.OPTIMIZE_PERFORMED:
+                return PackageDexOptimizer.DEX_OPT_PERFORMED;
+            case OptimizeResult.OPTIMIZE_CANCELLED:
+                return PackageDexOptimizer.DEX_OPT_CANCELLED;
+            default:
+                throw new IllegalArgumentException("OptimizeResult for "
+                        + result.getPackageOptimizeResults().get(0).getPackageName()
+                        + " has unsupported status " + status);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index 13f6ee7..f5557c4 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -18,6 +18,16 @@
 
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
+import android.annotation.Nullable;
+
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.pm.DexOptHelper;
+import com.android.server.pm.PackageManagerService;
+
+import dalvik.system.DexFile;
+
 /**
  * Options used for dexopt invocations.
  */
@@ -189,4 +199,133 @@
                 mSplitName,
                 mFlags);
     }
+
+    /**
+     * Returns an {@link OptimizeParams} instance corresponding to this object, for use with
+     * {@link com.android.server.art.ArtManagerLocal}.
+     *
+     * @param extraFlags extra {@link ArtFlags#OptimizeFlags} to set in the returned
+     *     {@code OptimizeParams} beyond those converted from this object
+     * @return null if the settings cannot be accurately represented, and hence the old
+     *     PackageManager/installd code paths need to be used.
+     */
+    public @Nullable OptimizeParams convertToOptimizeParams(/*@OptimizeFlags*/ int extraFlags) {
+        if (mSplitName != null) {
+            DexOptHelper.reportArtManagerFallback(
+                    mPackageName, "Request to optimize only split " + mSplitName);
+            return null;
+        }
+
+        /*@OptimizeFlags*/ int flags = extraFlags;
+        if ((mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) == 0
+                && DexFile.isProfileGuidedCompilerFilter(mCompilerFilter)) {
+            // ART Service doesn't support bypassing this, so not setting this flag is not
+            // supported.
+            DexOptHelper.reportArtManagerFallback(mPackageName,
+                    "DEXOPT_CHECK_FOR_PROFILES_UPDATES not set with profile compiler filter");
+            return null;
+        }
+        if ((mFlags & DEXOPT_FORCE) != 0) {
+            flags |= ArtFlags.FLAG_FORCE;
+        }
+        if ((mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0) {
+            flags |= ArtFlags.FLAG_FOR_SECONDARY_DEX;
+        } else {
+            flags |= ArtFlags.FLAG_FOR_PRIMARY_DEX;
+        }
+        if ((mFlags & DEXOPT_DOWNGRADE) != 0) {
+            flags |= ArtFlags.FLAG_SHOULD_DOWNGRADE;
+        }
+        if ((mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) == 0) {
+            // ART Service cannot be instructed to ignore a DM file if present, so not setting this
+            // flag is not supported.
+            DexOptHelper.reportArtManagerFallback(
+                    mPackageName, "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set");
+            return null;
+        }
+
+        /*@PriorityClassApi*/ int priority;
+        // Replicates logic in RunDex2Oat::PrepareCompilerRuntimeAndPerfConfigFlags in installd.
+        if ((mFlags & DEXOPT_BOOT_COMPLETE) != 0) {
+            if ((mFlags & DEXOPT_FOR_RESTORE) != 0) {
+                priority = ArtFlags.PRIORITY_INTERACTIVE_FAST;
+            } else {
+                // TODO(b/251903639): Repurpose DEXOPT_IDLE_BACKGROUND_JOB to choose new
+                // dalvik.vm.background-dex2oat-* properties.
+                priority = ArtFlags.PRIORITY_INTERACTIVE;
+            }
+        } else {
+            priority = ArtFlags.PRIORITY_BOOT;
+        }
+
+        // The following flags in mFlags are ignored:
+        //
+        // -  DEXOPT_AS_SHARED_LIBRARY: It's implicit with ART Service since it always looks at
+        //    <uses-library> rather than actual dependencies.
+        //
+        //    We don't require it to be set either. It's safe when switching between old and new
+        //    code paths since the only effect is that some packages may be unnecessarily compiled
+        //    without user profiles.
+        //
+        // -  DEXOPT_IDLE_BACKGROUND_JOB: Its only effect is to allow the debug variant dex2oatd to
+        //    be used, but ART Service never uses that (cf. Artd::GetDex2Oat in artd.cc).
+
+        String reason;
+        switch (mCompilationReason) {
+            case PackageManagerService.REASON_FIRST_BOOT:
+                reason = ReasonMapping.REASON_FIRST_BOOT;
+                break;
+            case PackageManagerService.REASON_BOOT_AFTER_OTA:
+                reason = ReasonMapping.REASON_BOOT_AFTER_OTA;
+                break;
+            case PackageManagerService.REASON_POST_BOOT:
+                // This reason will go away with the legacy dexopt code.
+                DexOptHelper.reportArtManagerFallback(
+                        mPackageName, "Unsupported compilation reason REASON_POST_BOOT");
+                return null;
+            case PackageManagerService.REASON_INSTALL:
+                reason = ReasonMapping.REASON_INSTALL;
+                break;
+            case PackageManagerService.REASON_INSTALL_FAST:
+                reason = ReasonMapping.REASON_INSTALL_FAST;
+                break;
+            case PackageManagerService.REASON_INSTALL_BULK:
+                reason = ReasonMapping.REASON_INSTALL_BULK;
+                break;
+            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY:
+                reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY;
+                break;
+            case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED:
+                reason = ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED;
+                break;
+            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+                reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+                break;
+            case PackageManagerService.REASON_BACKGROUND_DEXOPT:
+                reason = ReasonMapping.REASON_BG_DEXOPT;
+                break;
+            case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE:
+                reason = ReasonMapping.REASON_INACTIVE;
+                break;
+            case PackageManagerService.REASON_CMDLINE:
+                reason = ReasonMapping.REASON_CMDLINE;
+                break;
+            case PackageManagerService.REASON_SHARED:
+            case PackageManagerService.REASON_AB_OTA:
+                // REASON_SHARED shouldn't go into this code path - it's only used at lower levels
+                // in PackageDexOptimizer.
+                // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way
+                // either.
+                throw new UnsupportedOperationException(
+                        "ART Service unsupported compilation reason " + mCompilationReason);
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid compilation reason " + mCompilationReason);
+        }
+
+        return new OptimizeParams.Builder(reason, flags)
+                .setCompilerFilter(mCompilerFilter)
+                .setPriorityClass(priority)
+                .build();
+    }
 }