Replace "DexOptimizer" with "Dexopter".

Bug: 265248684
Test: atest ArtServiceTests
Ignore-AOSP-First: ART Services.
Change-Id: Ibc61b1c64bd25e1203ef9f38b98b2ba70c7b4c0b
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
new file mode 100644
index 0000000..6e220c7
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -0,0 +1,682 @@
+/*
+ * 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;
+
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+import static com.android.server.art.Utils.Abi;
+import static com.android.server.art.model.ArtFlags.DexoptFlags;
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+
+import android.R;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import dalvik.system.DexFile;
+
+import com.google.auto.value.AutoValue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
+    private static final String TAG = "Dexopter";
+
+    @NonNull protected final Injector mInjector;
+    @NonNull protected final PackageState mPkgState;
+    /** This is always {@code mPkgState.getAndroidPackage()} and guaranteed to be non-null. */
+    @NonNull protected final AndroidPackage mPkg;
+    @NonNull protected final DexoptParams mParams;
+    @NonNull protected final CancellationSignal mCancellationSignal;
+
+    protected Dexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
+            @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+            @NonNull CancellationSignal cancellationSignal) {
+        mInjector = injector;
+        mPkgState = pkgState;
+        mPkg = pkg;
+        mParams = params;
+        mCancellationSignal = cancellationSignal;
+        if (pkgState.getAppId() < 0) {
+            throw new IllegalStateException(
+                    "Package '" + pkgState.getPackageName() + "' has invalid app ID");
+        }
+    }
+
+    /**
+     * DO NOT use this method directly. Use {@link
+     * ArtManagerLocal#dexoptPackage(PackageManagerLocal.FilteredSnapshot, String,
+     * DexoptParams)}.
+     */
+    @NonNull
+    public final List<DexContainerFileDexoptResult> dexopt() throws RemoteException {
+        List<DexContainerFileDexoptResult> results = new ArrayList<>();
+
+        for (DexInfoType dexInfo : getDexInfoList()) {
+            ProfilePath profile = null;
+            boolean succeeded = true;
+            try {
+                if (!isDexoptable(dexInfo)) {
+                    continue;
+                }
+
+                String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo);
+                if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
+                    continue;
+                }
+
+                boolean needsToBeShared = needsToBeShared(dexInfo);
+                boolean isOtherReadable = true;
+                // If true, implies that the profile has changed since the last compilation.
+                boolean profileMerged = false;
+                if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) {
+                    if (needsToBeShared) {
+                        profile = initReferenceProfile(dexInfo);
+                    } else {
+                        Pair<ProfilePath, Boolean> pair = getOrInitReferenceProfile(dexInfo);
+                        if (pair != null) {
+                            profile = pair.first;
+                            isOtherReadable = pair.second;
+                        }
+                        ProfilePath mergedProfile = mergeProfiles(dexInfo, profile);
+                        if (mergedProfile != null) {
+                            if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
+                                mInjector.getArtd().deleteProfile(profile);
+                            }
+                            profile = mergedProfile;
+                            isOtherReadable = false;
+                            profileMerged = true;
+                        }
+                    }
+                    if (profile == null) {
+                        // A profile guided dexopt with no profile is essentially 'verify',
+                        // and dex2oat already makes this transformation. However, we need to
+                        // explicitly make this transformation here to guide the later decisions
+                        // such as whether the artifacts can be public and whether dexopt is needed.
+                        compilerFilter = needsToBeShared
+                                ? ReasonMapping.getCompilerFilterForShared()
+                                : "verify";
+                    }
+                }
+                boolean isProfileGuidedCompilerFilter =
+                        DexFile.isProfileGuidedCompilerFilter(compilerFilter);
+                Utils.check(isProfileGuidedCompilerFilter == (profile != null));
+
+                boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable)
+                        && isDexFilePublic(dexInfo);
+                Utils.check(Utils.implies(needsToBeShared, canBePublic));
+                PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic);
+
+                DexoptOptions dexoptOptions =
+                        getDexoptOptions(dexInfo, isProfileGuidedCompilerFilter);
+
+                for (Abi abi : getAllAbis(dexInfo)) {
+                    @DexoptResult.DexoptResultStatus int status = DexoptResult.DEXOPT_SKIPPED;
+                    long wallTimeMs = 0;
+                    long cpuTimeMs = 0;
+                    long sizeBytes = 0;
+                    long sizeBeforeBytes = 0;
+                    boolean isSkippedDueToStorageLow = false;
+                    try {
+                        var target = DexoptTarget.<DexInfoType>builder()
+                                             .setDexInfo(dexInfo)
+                                             .setIsa(abi.isa())
+                                             .setIsInDalvikCache(isInDalvikCache())
+                                             .setCompilerFilter(compilerFilter)
+                                             .build();
+                        var options = GetDexoptNeededOptions.builder()
+                                              .setProfileMerged(profileMerged)
+                                              .setFlags(mParams.getFlags())
+                                              .setNeedsToBePublic(needsToBeShared)
+                                              .build();
+
+                        GetDexoptNeededResult getDexoptNeededResult =
+                                getDexoptNeeded(target, options);
+
+                        if (!getDexoptNeededResult.isDexoptNeeded) {
+                            continue;
+                        }
+
+                        try {
+                            // `StorageManager.getAllocatableBytes` returns (free space + space used
+                            // by clearable cache - low storage threshold). Since we only compare
+                            // the result with 0, the clearable cache doesn't make a difference.
+                            // When the free space is below the threshold, there should be no
+                            // clearable cache left because system cleans up cache every minute.
+                            if ((mParams.getFlags() & ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) != 0
+                                    && mInjector.getStorageManager().getAllocatableBytes(
+                                               mPkg.getStorageUuid())
+                                            <= 0) {
+                                isSkippedDueToStorageLow = true;
+                                continue;
+                            }
+                        } catch (IOException e) {
+                            Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
+                        }
+
+                        IArtdCancellationSignal artdCancellationSignal =
+                                mInjector.getArtd().createCancellationSignal();
+                        mCancellationSignal.setOnCancelListener(() -> {
+                            try {
+                                artdCancellationSignal.cancel();
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "An error occurred when sending a cancellation signal",
+                                        e);
+                            }
+                        });
+
+                        ArtdDexoptResult dexoptResult = dexoptFile(target, profile,
+                                getDexoptNeededResult, permissionSettings,
+                                mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal);
+                        status = dexoptResult.cancelled ? DexoptResult.DEXOPT_CANCELLED
+                                                        : DexoptResult.DEXOPT_PERFORMED;
+                        wallTimeMs = dexoptResult.wallTimeMs;
+                        cpuTimeMs = dexoptResult.cpuTimeMs;
+                        sizeBytes = dexoptResult.sizeBytes;
+                        sizeBeforeBytes = dexoptResult.sizeBeforeBytes;
+
+                        if (status == DexoptResult.DEXOPT_CANCELLED) {
+                            return results;
+                        }
+                    } catch (ServiceSpecificException e) {
+                        // Log the error and continue.
+                        Log.e(TAG,
+                                String.format("Failed to dexopt [packageName = %s, dexPath = %s, "
+                                                + "isa = %s, classLoaderContext = %s]",
+                                        mPkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
+                                        dexInfo.classLoaderContext()),
+                                e);
+                        status = DexoptResult.DEXOPT_FAILED;
+                    } finally {
+                        results.add(DexContainerFileDexoptResult.create(dexInfo.dexPath(),
+                                abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
+                                cpuTimeMs, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow));
+                        if (status != DexoptResult.DEXOPT_SKIPPED
+                                && status != DexoptResult.DEXOPT_PERFORMED) {
+                            succeeded = false;
+                        }
+                        // Make sure artd does not leak even if the caller holds
+                        // `mCancellationSignal` forever.
+                        mCancellationSignal.setOnCancelListener(null);
+                    }
+                }
+
+                if (profile != null && succeeded) {
+                    if (profile.getTag() == ProfilePath.tmpProfilePath) {
+                        // Commit the profile only if dexopt succeeds.
+                        if (commitProfileChanges(profile.getTmpProfilePath())) {
+                            profile = null;
+                        }
+                    }
+                    if (profileMerged) {
+                        // Note that this is just an optimization, to reduce the amount of data that
+                        // the runtime writes on every profile save. The profile merge result on the
+                        // next run won't change regardless of whether the cleanup is done or not
+                        // because profman only looks at the diff.
+                        // A caveat is that it may delete more than what has been merged, if the
+                        // runtime writes additional entries between the merge and the cleanup, but
+                        // this is fine because the runtime writes all JITed classes and methods on
+                        // every save and the additional entries will likely be written back on the
+                        // next save.
+                        cleanupCurProfiles(dexInfo);
+                    }
+                }
+            } finally {
+                if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
+                    mInjector.getArtd().deleteProfile(profile);
+                }
+            }
+        }
+
+        return results;
+    }
+
+    @NonNull
+    private String adjustCompilerFilter(
+            @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) {
+        if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) {
+            String systemUiCompilerFilter = getSystemUiCompilerFilter();
+            if (!systemUiCompilerFilter.isEmpty()) {
+                return systemUiCompilerFilter;
+            }
+        }
+
+        // We force vmSafeMode on debuggable apps as well:
+        //  - the runtime ignores their compiled code
+        //  - they generally have lots of methods that could make the compiler used run out of
+        //    memory (b/130828957)
+        // Note that forcing the compiler filter here applies to all compilations (even if they
+        // are done via adb shell commands). This is okay because the runtime will ignore the
+        // compiled code anyway.
+        if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) {
+            return DexFile.getSafeModeCompilerFilter(targetCompilerFilter);
+        }
+
+        // We cannot do AOT compilation if we don't have a valid class loader context.
+        if (dexInfo.classLoaderContext() == null) {
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
+        }
+
+        // This application wants to use the embedded dex in the APK, rather than extracted or
+        // locally compiled variants, so we only verify it.
+        // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat
+        // won't extract the dex code because the APK is uncompressed, and the assumption is that
+        // such applications always use uncompressed APKs.
+        if (mPkg.isUseEmbeddedDex()) {
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
+        }
+
+        return targetCompilerFilter;
+    }
+
+    @NonNull
+    private String getSystemUiCompilerFilter() {
+        String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter");
+        if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) {
+            throw new IllegalStateException(
+                    "Got invalid compiler filter '" + compilerFilter + "' for System UI");
+        }
+        return compilerFilter;
+    }
+
+    /**
+     * Gets the existing reference profile if exists, or initializes a reference profile from an
+     * external profile.
+     *
+     * @return A pair where the first element is the found or initialized profile, and the second
+     *         element is true if the profile is readable by others. Or null if there is no
+     *         reference profile or external profile to use.
+     */
+    @Nullable
+    private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
+            throws RemoteException {
+        ProfilePath refProfile = buildRefProfilePath(dexInfo);
+        try {
+            if (mInjector.getArtd().isProfileUsable(refProfile, dexInfo.dexPath())) {
+                boolean isOtherReadable = mInjector.getArtd().getProfileVisibility(refProfile)
+                        == FileVisibility.OTHER_READABLE;
+                return Pair.create(refProfile, isOtherReadable);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to use the existing reference profile "
+                            + AidlUtils.toString(refProfile),
+                    e);
+        }
+
+        ProfilePath initializedProfile = initReferenceProfile(dexInfo);
+        return initializedProfile != null ? Pair.create(initializedProfile, true) : null;
+    }
+
+    @NonNull
+    private DexoptOptions getDexoptOptions(
+            @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) {
+        DexoptOptions dexoptOptions = new DexoptOptions();
+        dexoptOptions.compilationReason = mParams.getReason();
+        dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion();
+        dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable();
+        // Generating a meaningful app image needs a profile to determine what to include in the
+        // image. Otherwise, the app image will be nearly empty.
+        dexoptOptions.generateAppImage =
+                isProfileGuidedFilter && isAppImageAllowed(dexInfo) && isAppImageEnabled();
+        dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled();
+        return dexoptOptions;
+    }
+
+    private boolean isAlwaysDebuggable() {
+        return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */);
+    }
+
+    private boolean isAppImageEnabled() {
+        return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty();
+    }
+
+    private boolean isHiddenApiPolicyEnabled() {
+        return mPkgState.getHiddenApiEnforcementPolicy()
+                != ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
+    }
+
+    @NonNull
+    GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target,
+            @NonNull GetDexoptNeededOptions options) throws RemoteException {
+        int dexoptTrigger = getDexoptTrigger(target, options);
+
+        // The result should come from artd even if all the bits of `dexoptTrigger` are set
+        // because the result also contains information about the usable VDEX file.
+        // Note that the class loader context can be null. In that case, we intentionally pass the
+        // null value down to lower levels to indicate that the class loader context check should be
+        // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`).
+        GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
+                target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+                target.compilerFilter(), dexoptTrigger);
+
+        return result;
+    }
+
+    int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target,
+            @NonNull GetDexoptNeededOptions options) throws RemoteException {
+        if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
+            return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME
+                    | DexoptTrigger.COMPILER_FILTER_IS_WORSE
+                    | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+        }
+
+        if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
+            return DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        }
+
+        int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+                | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+        if (options.profileMerged()) {
+            dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME;
+        }
+
+        ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPath(
+                target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache());
+
+        if (options.needsToBePublic()
+                && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath)
+                        == FileVisibility.NOT_OTHER_READABLE) {
+            // Typically, this happens after an app starts being used by other apps.
+            // This case should be the same as force as we have no choice but to trigger a new
+            // dexopt.
+            dexoptTrigger |=
+                    DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+        }
+
+        return dexoptTrigger;
+    }
+
+    private ArtdDexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target,
+            @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
+            @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
+            @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)
+            throws RemoteException {
+        OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(),
+                target.isa(), target.isInDalvikCache(), permissionSettings);
+
+        VdexPath inputVdex =
+                getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
+
+        DexMetadataPath dmFile = getDmFile(target.dexInfo());
+        if (dmFile != null
+                && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
+            // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
+            // "install-dm").
+            // Note that this only applies to reasons for app install because the goal is to give
+            // Play a signal that a DM file is downloaded at install time. We actually pass the DM
+            // file regardless of the compilation reason, but we don't append a suffix when the
+            // compilation reason is not a reason for app install.
+            // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by
+            // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The
+            // compilation reason can still be "install-dm" even if dex2oat left all contents of the
+            // DM file unused or an empty DM file is passed to dex2oat.
+            dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
+        }
+
+        return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
+                target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
+                dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
+    }
+
+    @Nullable
+    private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult,
+            @NonNull String dexPath, @NonNull String isa) {
+        if (!getDexoptNeededResult.isVdexUsable) {
+            return null;
+        }
+        switch (getDexoptNeededResult.artifactsLocation) {
+            case ArtifactsLocation.DALVIK_CACHE:
+                return VdexPath.artifactsPath(
+                        AidlUtils.buildArtifactsPath(dexPath, isa, true /* isInDalvikCache */));
+            case ArtifactsLocation.NEXT_TO_DEX:
+                return VdexPath.artifactsPath(
+                        AidlUtils.buildArtifactsPath(dexPath, isa, false /* isInDalvikCache */));
+            case ArtifactsLocation.DM:
+                // The DM file is passed to dex2oat as a separate flag whenever it exists.
+                return null;
+            default:
+                // This should never happen as the value is got from artd.
+                throw new IllegalStateException(
+                        "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
+        }
+    }
+
+    @Nullable
+    private DexMetadataPath getDmFile(@NonNull DexInfoType dexInfo) throws RemoteException {
+        DexMetadataPath path = buildDmPath(dexInfo);
+        if (path == null) {
+            return null;
+        }
+        try {
+            if (mInjector.getArtd().getDmFileVisibility(path) != FileVisibility.NOT_FOUND) {
+                return path;
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG, "Failed to check DM file for " + dexInfo.dexPath(), e);
+        }
+        return null;
+    }
+
+    private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
+        try {
+            mInjector.getArtd().commitTmpProfile(profile);
+            return true;
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG, "Failed to commit profile changes " + AidlUtils.toString(profile.finalPath),
+                    e);
+            return false;
+        }
+    }
+
+    @Nullable
+    private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo,
+            @Nullable ProfilePath referenceProfile) throws RemoteException {
+        OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */);
+
+        try {
+            if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output,
+                        List.of(dexInfo.dexPath()), new MergeProfileOptions())) {
+                return ProfilePath.tmpProfilePath(output.profilePath);
+            }
+        } catch (ServiceSpecificException e) {
+            Log.e(TAG,
+                    "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
+                    e);
+        }
+
+        return null;
+    }
+
+    private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException {
+        for (ProfilePath profile : getCurProfiles(dexInfo)) {
+            mInjector.getArtd().deleteProfile(profile);
+        }
+    }
+
+    // Methods to be implemented by child classes.
+
+    /** Returns true if the artifacts should be written to the global dalvik-cache directory. */
+    protected abstract boolean isInDalvikCache();
+
+    /** Returns information about all dex files. */
+    @NonNull protected abstract List<DexInfoType> getDexInfoList();
+
+    /** Returns true if the given dex file should be dexopted. */
+    protected abstract boolean isDexoptable(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns true if the artifacts should be shared with other apps. Note that this must imply
+     * {@link #isDexFilePublic(DexInfoType)}.
+     */
+    protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+     * (S_IROTH).
+     */
+    protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns a reference profile initialized from an external profile (e.g., a DM profile) if
+     * one exists, or null otherwise.
+     */
+    @Nullable
+    protected abstract ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo)
+            throws RemoteException;
+
+    /** Returns the permission settings to use for the artifacts of the given dex file. */
+    @NonNull
+    protected abstract PermissionSettings getPermissionSettings(
+            @NonNull DexInfoType dexInfo, boolean canBePublic);
+
+    /** Returns all ABIs that the given dex file should be compiled for. */
+    @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo);
+
+    /** Returns the path to the reference profile of the given dex file. */
+    @NonNull protected abstract ProfilePath buildRefProfilePath(@NonNull DexInfoType dexInfo);
+
+    /** Returns true if app image (--app-image-fd) is allowed. */
+    protected abstract boolean isAppImageAllowed(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns the data structure that represents the temporary profile to use during processing.
+     */
+    @NonNull
+    protected abstract OutputProfile buildOutputProfile(
+            @NonNull DexInfoType dexInfo, boolean isPublic);
+
+    /** Returns the paths to the current profiles of the given dex file. */
+    @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo);
+
+    /**
+     * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file
+     * should be passed.
+     */
+    @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo);
+
+    @AutoValue
+    abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> {
+        abstract @NonNull DexInfoType dexInfo();
+        abstract @NonNull String isa();
+        abstract boolean isInDalvikCache();
+        abstract @NonNull String compilerFilter();
+
+        static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
+            return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>();
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder<DexInfoType extends DetailedDexInfo> {
+            abstract Builder setDexInfo(@NonNull DexInfoType value);
+            abstract Builder setIsa(@NonNull String value);
+            abstract Builder setIsInDalvikCache(boolean value);
+            abstract Builder setCompilerFilter(@NonNull String value);
+            abstract DexoptTarget<DexInfoType> build();
+        }
+    }
+
+    @AutoValue
+    abstract static class GetDexoptNeededOptions {
+        abstract @DexoptFlags int flags();
+        abstract boolean profileMerged();
+        abstract boolean needsToBePublic();
+
+        static Builder builder() {
+            return new AutoValue_Dexopter_GetDexoptNeededOptions.Builder();
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            abstract Builder setFlags(@DexoptFlags int value);
+            abstract Builder setProfileMerged(boolean value);
+            abstract Builder setNeedsToBePublic(boolean value);
+            abstract GetDexoptNeededOptions build();
+        }
+    }
+
+    /**
+     * Injector pattern for testing purpose.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public static class Injector {
+        @NonNull private final Context mContext;
+
+        public Injector(@NonNull Context context) {
+            mContext = context;
+
+            // Call the getters for various dependencies, to ensure correct initialization order.
+            getUserManager();
+            getDexUseManager();
+            getStorageManager();
+            ArtModuleServiceInitializer.getArtModuleServiceManager();
+        }
+
+        public boolean isSystemUiPackage(@NonNull String packageName) {
+            return packageName.equals(mContext.getString(R.string.config_systemUi));
+        }
+
+        @NonNull
+        public UserManager getUserManager() {
+            return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+        }
+
+        @NonNull
+        public DexUseManagerLocal getDexUseManager() {
+            return Objects.requireNonNull(
+                    LocalManagerRegistry.getManager(DexUseManagerLocal.class));
+        }
+
+        @NonNull
+        public IArtd getArtd() {
+            return Utils.getArtd();
+        }
+
+        @NonNull
+        public StorageManager getStorageManager() {
+            return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
+        }
+    }
+}