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