Refactor PrimaryDexOptimizer to prepare for secondary dex compilation.
This CL extracts common dex compilation logic into a base class
DexOptimizer, and only leave primary-dex-specific logic in
PrimaryDexOptimizer.
This is a no-op change. It only moves code around.
Bug: 249984283
Test: atest ArtServiceTests
Ignore-AOSP-First: ART Services.
Change-Id: Ifca8ef1f8079afb4d8ce0826f0f9e4c21c3ce1d8
diff --git a/libartservice/service/java/com/android/server/art/DexOptHelper.java b/libartservice/service/java/com/android/server/art/DexOptHelper.java
index b3aa9d5..c87d71b 100644
--- a/libartservice/service/java/com/android/server/art/DexOptHelper.java
+++ b/libartservice/service/java/com/android/server/art/DexOptHelper.java
@@ -100,8 +100,9 @@
wakeLock.acquire(WAKE_LOCK_TIMEOUT_MS);
if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
- results.addAll(mInjector.getPrimaryDexOptimizer().dexopt(
- pkgState, pkg, params, cancellationSignal));
+ results.addAll(
+ mInjector.getPrimaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
+ .dexopt());
if (hasCancelledResult.get()) {
return createResult.get();
}
@@ -158,8 +159,10 @@
}
@NonNull
- PrimaryDexOptimizer getPrimaryDexOptimizer() {
- return new PrimaryDexOptimizer(mContext);
+ PrimaryDexOptimizer getPrimaryDexOptimizer(@NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ return new PrimaryDexOptimizer(mContext, pkgState, pkg, params, cancellationSignal);
}
@NonNull
diff --git a/libartservice/service/java/com/android/server/art/DexOptimizer.java b/libartservice/service/java/com/android/server/art/DexOptimizer.java
new file mode 100644
index 0000000..078f698
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexOptimizer.java
@@ -0,0 +1,582 @@
+/*
+ * 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.OptimizeFlags;
+import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
+
+import android.R;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DetailedDexInfo;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+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.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** @hide */
+public abstract class DexOptimizer<DexInfoType extends DetailedDexInfo> {
+ private static final String TAG = "DexOptimizer";
+
+ @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 OptimizeParams mParams;
+ @NonNull protected final CancellationSignal mCancellationSignal;
+
+ protected DexOptimizer(@NonNull Injector injector, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams 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#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
+ * OptimizeParams)}.
+ */
+ @NonNull
+ public final List<DexContainerFileOptimizeResult> dexopt() throws RemoteException {
+ List<DexContainerFileOptimizeResult> results = new ArrayList<>();
+
+ String targetCompilerFilter = adjustCompilerFilter(mParams.getCompilerFilter());
+ if (targetCompilerFilter.equals(OptimizeParams.COMPILER_FILTER_NOOP)) {
+ return results;
+ }
+
+ for (DexInfoType dexInfo : getDexInfoList()) {
+ ProfilePath profile = null;
+ boolean succeeded = true;
+ try {
+ if (!isOptimizable(dexInfo)) {
+ continue;
+ }
+
+ String compilerFilter = targetCompilerFilter;
+
+ 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 optimization 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;
+ Utils.check(Utils.implies(needsToBeShared, canBePublic));
+ PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic);
+
+ DexoptOptions dexoptOptions = getDexoptOptions(isProfileGuidedCompilerFilter);
+
+ for (Abi abi : getAllAbis(dexInfo)) {
+ @OptimizeResult.OptimizeStatus int status = OptimizeResult.OPTIMIZE_SKIPPED;
+ long wallTimeMs = 0;
+ long cpuTimeMs = 0;
+ try {
+ DexoptTarget target = DexoptTarget.builder()
+ .setDexInfo(dexInfo)
+ .setIsa(abi.isa())
+ .setIsInDalvikCache(isInDalvikCache())
+ .setCompilerFilter(compilerFilter)
+ .build();
+ GetDexoptNeededOptions options =
+ GetDexoptNeededOptions.builder()
+ .setProfileMerged(profileMerged)
+ .setFlags(mParams.getFlags())
+ .setNeedsToBePublic(needsToBeShared)
+ .build();
+
+ GetDexoptNeededResult getDexoptNeededResult =
+ getDexoptNeeded(target, options);
+
+ if (!getDexoptNeededResult.isDexoptNeeded) {
+ continue;
+ }
+
+ 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);
+ }
+ });
+
+ DexoptResult dexoptResult = dexoptFile(target, profile,
+ getDexoptNeededResult, permissionSettings,
+ mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal);
+ status = dexoptResult.cancelled ? OptimizeResult.OPTIMIZE_CANCELLED
+ : OptimizeResult.OPTIMIZE_PERFORMED;
+ wallTimeMs = dexoptResult.wallTimeMs;
+ cpuTimeMs = dexoptResult.cpuTimeMs;
+
+ if (status == OptimizeResult.OPTIMIZE_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 = OptimizeResult.OPTIMIZE_FAILED;
+ } finally {
+ results.add(new DexContainerFileOptimizeResult(dexInfo.dexPath(),
+ abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
+ cpuTimeMs));
+ if (status != OptimizeResult.OPTIMIZE_SKIPPED
+ && status != OptimizeResult.OPTIMIZE_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) {
+ 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);
+ }
+
+ 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(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() && 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() {
+ if (mPkg.isSignedWithPlatformKey()) {
+ return false;
+ }
+ if (mPkgState.isSystem() || mPkgState.isUpdatedSystemApp()) {
+ // TODO(b/236389629): Check whether the app is in hidden api whitelist.
+ return !mPkg.isUsesNonSdkApi();
+ }
+ return true;
+ }
+
+ @NonNull
+ GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget 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.
+ GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
+ target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+ target.compilerFilter(), dexoptTrigger);
+
+ return result;
+ }
+
+ int getDexoptTrigger(@NonNull DexoptTarget 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 DexoptResult dexoptFile(@NonNull DexoptTarget 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());
+
+ return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
+ target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
+ 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:
+ return VdexPath.dexMetadataPath(AidlUtils.buildDexMetadataPath(dexPath));
+ default:
+ // This should never happen as the value is got from artd.
+ throw new IllegalStateException(
+ "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
+ }
+ }
+
+ 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, dexInfo.dexPath())) {
+ 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 optimized. */
+ protected abstract boolean isOptimizable(@NonNull DexInfoType dexInfo) throws RemoteException;
+
+ /** Returns true if the artifacts should be shared with other apps. */
+ protected abstract boolean needsToBeShared(@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();
+
+ /**
+ * 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);
+
+ @AutoValue
+ abstract static class DexoptTarget {
+ abstract @NonNull DetailedDexInfo dexInfo();
+ abstract @NonNull String isa();
+ abstract boolean isInDalvikCache();
+ abstract @NonNull String compilerFilter();
+
+ static Builder builder() {
+ return new AutoValue_DexOptimizer_DexoptTarget.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setDexInfo(@NonNull DetailedDexInfo value);
+ abstract Builder setIsa(@NonNull String value);
+ abstract Builder setIsInDalvikCache(boolean value);
+ abstract Builder setCompilerFilter(@NonNull String value);
+ abstract DexoptTarget build();
+ }
+ }
+
+ @AutoValue
+ abstract static class GetDexoptNeededOptions {
+ abstract @OptimizeFlags int flags();
+ abstract boolean profileMerged();
+ abstract boolean needsToBePublic();
+
+ static Builder builder() {
+ return new AutoValue_DexOptimizer_GetDexoptNeededOptions.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setFlags(@OptimizeFlags int value);
+ abstract Builder setProfileMerged(boolean value);
+ abstract Builder setNeedsToBePublic(boolean value);
+ abstract GetDexoptNeededOptions build();
+ }
+ }
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ @NonNull private final Context mContext;
+
+ Injector(@NonNull Context context) {
+ mContext = context;
+ }
+
+ boolean isSystemUiPackage(@NonNull String packageName) {
+ return packageName.equals(mContext.getString(R.string.config_systemUi));
+ }
+
+ @NonNull
+ UserManager getUserManager() {
+ return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
+ }
+
+ @NonNull
+ DexUseManager getDexUseManager() {
+ return DexUseManager.getInstance();
+ }
+
+ @NonNull
+ IArtd getArtd() {
+ return Utils.getArtd();
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/DexUseManager.java b/libartservice/service/java/com/android/server/art/DexUseManager.java
index 5c023e1..9cbca54 100644
--- a/libartservice/service/java/com/android/server/art/DexUseManager.java
+++ b/libartservice/service/java/com/android/server/art/DexUseManager.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.DetailedDexInfo;
import com.android.server.art.proto.DexUseProto;
import com.android.server.art.proto.Int32Value;
import com.android.server.art.proto.PackageDexUseProto;
@@ -343,7 +344,7 @@
*/
@Immutable
@AutoValue
- public abstract static class SecondaryDexInfo {
+ public abstract static class SecondaryDexInfo implements DetailedDexInfo {
// Special encoding used to denote a foreign ClassLoader was found when trying to encode
// class loader contexts for each classpath element in a ClassLoader.
// Must be in sync with `kUnsupportedClassLoaderContextEncoding` in
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
index ef7d659..ea1b20a 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
@@ -16,17 +16,11 @@
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.OutputArtifacts.PermissionSettings.SeContext;
import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
-import static com.android.server.art.ProfilePath.TmpProfilePath;
-import static com.android.server.art.ProfilePath.WritableProfilePath;
import static com.android.server.art.Utils.Abi;
-import static com.android.server.art.model.ArtFlags.OptimizeFlags;
-import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
-import android.R;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -34,15 +28,11 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
-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.PackageManagerLocal;
@@ -58,264 +48,60 @@
import java.util.List;
/** @hide */
-public class PrimaryDexOptimizer {
+public class PrimaryDexOptimizer extends DexOptimizer<DetailedPrimaryDexInfo> {
private static final String TAG = "PrimaryDexOptimizer";
- @NonNull private final Injector mInjector;
+ private final int mSharedGid;
- public PrimaryDexOptimizer(@NonNull Context context) {
- this(new Injector(context));
+ public PrimaryDexOptimizer(@NonNull Context context, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ this(new Injector(context), pkgState, pkg, params, cancellationSignal);
}
@VisibleForTesting
- public PrimaryDexOptimizer(@NonNull Injector injector) {
- mInjector = injector;
- }
-
- /**
- * DO NOT use this method directly. Use {@link
- * ArtManagerLocal#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
- * OptimizeParams)}.
- */
- @NonNull
- public List<DexContainerFileOptimizeResult> dexopt(@NonNull PackageState pkgState,
+ public PrimaryDexOptimizer(@NonNull Injector injector, @NonNull PackageState pkgState,
@NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
- @NonNull CancellationSignal cancellationSignal) throws RemoteException {
- List<DexContainerFileOptimizeResult> results = new ArrayList<>();
+ @NonNull CancellationSignal cancellationSignal) {
+ super(injector, pkgState, pkg, params, cancellationSignal);
- int appId = pkgState.getAppId();
- if (appId < 0) {
- throw new IllegalStateException(
- "Package '" + pkgState.getPackageName() + "' has invalid app ID");
- }
- int sharedGid = UserHandle.getSharedAppGid(appId);
- if (sharedGid < 0) {
+ mSharedGid = UserHandle.getSharedAppGid(pkgState.getAppId());
+ if (mSharedGid < 0) {
throw new IllegalStateException(
String.format("Unable to get shared gid for package '%s' (app ID: %d)",
- pkgState.getPackageName(), appId));
+ pkgState.getPackageName(), pkgState.getAppId()));
}
-
- String targetCompilerFilter =
- adjustCompilerFilter(pkgState, pkg, params.getCompilerFilter(), params.getReason());
- if (targetCompilerFilter.equals(OptimizeParams.COMPILER_FILTER_NOOP)) {
- return results;
- }
-
- boolean isInDalvikCache = Utils.isInDalvikCache(pkgState);
-
- for (DetailedPrimaryDexInfo dexInfo : PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
- ProfilePath profile = null;
- boolean succeeded = true;
- try {
- if (!dexInfo.hasCode()) {
- continue;
- }
-
- // TODO(jiakaiz): Support optimizing a single split.
-
- String compilerFilter = targetCompilerFilter;
-
- boolean needsToBeShared = isSharedLibrary(pkg)
- || mInjector.getDexUseManager().isPrimaryDexUsedByOtherApps(
- pkgState.getPackageName(), dexInfo.dexPath());
- 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(pkgState, dexInfo, appId, sharedGid);
- } else {
- Pair<ProfilePath, Boolean> pair =
- getOrInitReferenceProfile(pkgState, dexInfo, appId, sharedGid);
- if (pair != null) {
- profile = pair.first;
- isOtherReadable = pair.second;
- }
- ProfilePath mergedProfile =
- mergeProfiles(pkgState, dexInfo, appId, sharedGid, 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 optimization 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;
- Utils.check(Utils.implies(needsToBeShared, canBePublic));
- PermissionSettings permissionSettings =
- getPermissionSettings(sharedGid, canBePublic);
-
- DexoptOptions dexoptOptions =
- getDexoptOptions(pkgState, pkg, params, isProfileGuidedCompilerFilter);
-
- for (Abi abi : Utils.getAllAbis(pkgState)) {
- @OptimizeResult.OptimizeStatus int status = OptimizeResult.OPTIMIZE_SKIPPED;
- long wallTimeMs = 0;
- long cpuTimeMs = 0;
- try {
- DexoptTarget target = DexoptTarget.builder()
- .setDexInfo(dexInfo)
- .setIsa(abi.isa())
- .setIsInDalvikCache(isInDalvikCache)
- .setCompilerFilter(compilerFilter)
- .build();
- GetDexoptNeededOptions options =
- GetDexoptNeededOptions.builder()
- .setProfileMerged(profileMerged)
- .setFlags(params.getFlags())
- .setNeedsToBePublic(needsToBeShared)
- .build();
-
- GetDexoptNeededResult getDexoptNeededResult =
- getDexoptNeeded(target, options);
-
- if (!getDexoptNeededResult.isDexoptNeeded) {
- continue;
- }
-
- IArtdCancellationSignal artdCancellationSignal =
- mInjector.getArtd().createCancellationSignal();
- cancellationSignal.setOnCancelListener(() -> {
- try {
- artdCancellationSignal.cancel();
- } catch (RemoteException e) {
- Log.e(TAG, "An error occurred when sending a cancellation signal",
- e);
- }
- });
-
- DexoptResult dexoptResult = dexoptFile(target, profile,
- getDexoptNeededResult, permissionSettings,
- params.getPriorityClass(), dexoptOptions, artdCancellationSignal);
- status = dexoptResult.cancelled ? OptimizeResult.OPTIMIZE_CANCELLED
- : OptimizeResult.OPTIMIZE_PERFORMED;
- wallTimeMs = dexoptResult.wallTimeMs;
- cpuTimeMs = dexoptResult.cpuTimeMs;
-
- if (status == OptimizeResult.OPTIMIZE_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]",
- pkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
- dexInfo.classLoaderContext()),
- e);
- status = OptimizeResult.OPTIMIZE_FAILED;
- } finally {
- results.add(new DexContainerFileOptimizeResult(dexInfo.dexPath(),
- abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
- cpuTimeMs));
- if (status != OptimizeResult.OPTIMIZE_SKIPPED
- && status != OptimizeResult.OPTIMIZE_PERFORMED) {
- succeeded = false;
- }
- // Make sure artd does not leak even if the caller holds
- // `cancellationSignal` forever.
- cancellationSignal.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(pkgState, dexInfo);
- }
- }
- } finally {
- if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
- mInjector.getArtd().deleteProfile(profile);
- }
- }
- }
-
- return results;
}
+ @Override
+ protected boolean isInDalvikCache() {
+ return Utils.isInDalvikCache(mPkgState);
+ }
+
+ @Override
@NonNull
- private String adjustCompilerFilter(@NonNull PackageState pkgState,
- @NonNull AndroidPackage pkg, @NonNull String targetCompilerFilter,
- @NonNull String reason) {
- if (mInjector.isSystemUiPackage(pkgState.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 (pkg.isVmSafeMode() || pkg.isDebuggable()) {
- return DexFile.getSafeModeCompilerFilter(targetCompilerFilter);
- }
-
- return targetCompilerFilter;
+ protected List<DetailedPrimaryDexInfo> getDexInfoList() {
+ return PrimaryDexUtils.getDetailedDexInfo(mPkgState, mPkg);
}
- @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;
+ @Override
+ protected boolean isOptimizable(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ // TODO(jiakaiz): Support optimizing a single split.
+ return dexInfo.hasCode();
}
- boolean isSharedLibrary(@NonNull AndroidPackage pkg) {
- // TODO(b/242688548): Package manager should provide a better API for this.
- return !TextUtils.isEmpty(pkg.getSdkLibraryName())
- || !TextUtils.isEmpty(pkg.getStaticSharedLibraryName())
- || !pkg.getLibraryNames().isEmpty();
+ @Override
+ protected boolean needsToBeShared(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ return isSharedLibrary()
+ || mInjector.getDexUseManager().isPrimaryDexUsedByOtherApps(
+ mPkgState.getPackageName(), dexInfo.dexPath());
}
- /**
- * Returns a reference profile initialized from a prebuilt profile or a DM profile if exists, or
- * null otherwise.
- */
+ @Override
@Nullable
- private ProfilePath initReferenceProfile(@NonNull PackageState pkgState,
- @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid) throws RemoteException {
- String profileName = getProfileName(dexInfo.splitName());
- OutputProfile output = AidlUtils.buildOutputProfileForPrimary(
- pkgState.getPackageName(), profileName, uid, gid, true /* isPublic */);
+ protected ProfilePath initReferenceProfile(@NonNull DetailedPrimaryDexInfo dexInfo)
+ throws RemoteException {
+ OutputProfile output = buildOutputProfile(dexInfo, true /* isPublic */);
ProfilePath prebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath());
try {
@@ -350,44 +136,10 @@
return null;
}
- /**
- * 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 PackageState pkgState,
- @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid) throws RemoteException {
- String profileName = getProfileName(dexInfo.splitName());
- ProfilePath refProfile =
- AidlUtils.buildProfilePathForPrimaryRef(pkgState.getPackageName(), profileName);
- 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(pkgState, dexInfo, uid, gid);
- return initializedProfile != null ? Pair.create(initializedProfile, true) : null;
- }
-
+ @Override
@NonNull
- public String getProfileName(@Nullable String splitName) {
- return splitName == null ? "primary" : splitName + ".split";
- }
-
- @NonNull
- PermissionSettings getPermissionSettings(int sharedGid, boolean canBePublic) {
+ protected PermissionSettings getPermissionSettings(
+ @NonNull DetailedPrimaryDexInfo dexInfo, boolean canBePublic) {
// The files and directories should belong to the system so that Package Manager can manage
// them (e.g., move them around).
// We don't need the "read" bit for "others" on the directories because others only need to
@@ -395,262 +147,66 @@
FsPermission dirFsPermission = AidlUtils.buildFsPermission(Process.SYSTEM_UID,
Process.SYSTEM_UID, false /* isOtherReadable */, true /* isOtherExecutable */);
FsPermission fileFsPermission =
- AidlUtils.buildFsPermission(Process.SYSTEM_UID, sharedGid, canBePublic);
+ AidlUtils.buildFsPermission(Process.SYSTEM_UID, mSharedGid, canBePublic);
// For primary dex, we can use the default SELinux context.
SeContext seContext = null;
return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext);
}
+ @Override
@NonNull
- private DexoptOptions getDexoptOptions(@NonNull PackageState pkgState,
- @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
- boolean isProfileGuidedFilter) {
- DexoptOptions dexoptOptions = new DexoptOptions();
- dexoptOptions.compilationReason = params.getReason();
- dexoptOptions.targetSdkVersion = pkg.getTargetSdkVersion();
- dexoptOptions.debuggable = pkg.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.
- // Additionally, disable app images if the app requests for the splits to be loaded in
- // isolation because app images are unsupported for multiple class loaders (b/72696798).
- dexoptOptions.generateAppImage = isProfileGuidedFilter
- && !PrimaryDexUtils.isIsolatedSplitLoading(pkg) && isAppImageEnabled();
- dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled(pkgState, pkg);
- return dexoptOptions;
+ protected List<Abi> getAllAbis(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ return Utils.getAllAbis(mPkgState);
}
- 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(
- @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) {
- if (pkg.isSignedWithPlatformKey()) {
- return false;
- }
- if (pkgState.isSystem() || pkgState.isUpdatedSystemApp()) {
- // TODO(b/236389629): Check whether the app is in hidden api whitelist.
- return !pkg.isUsesNonSdkApi();
- }
- return true;
- }
-
+ @Override
@NonNull
- GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget 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.
- GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
- target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
- target.compilerFilter(), dexoptTrigger);
-
- return result;
- }
-
- int getDexoptTrigger(@NonNull DexoptTarget 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 DexoptResult dexoptFile(@NonNull DexoptTarget 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());
-
- return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
- target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
- 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:
- return VdexPath.dexMetadataPath(AidlUtils.buildDexMetadataPath(dexPath));
- default:
- // This should never happen as the value is got from artd.
- throw new IllegalStateException(
- "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
- }
- }
-
- 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 PackageState pkgState,
- @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid,
- @Nullable ProfilePath referenceProfile) throws RemoteException {
+ protected ProfilePath buildRefProfilePath(@NonNull DetailedPrimaryDexInfo dexInfo) {
String profileName = getProfileName(dexInfo.splitName());
- OutputProfile output = AidlUtils.buildOutputProfileForPrimary(
- pkgState.getPackageName(), profileName, uid, gid, false /* isPublic */);
-
- try {
- if (mInjector.getArtd().mergeProfiles(getCurProfiles(pkgState, dexInfo),
- referenceProfile, output, dexInfo.dexPath())) {
- return ProfilePath.tmpProfilePath(output.profilePath);
- }
- } catch (ServiceSpecificException e) {
- Log.e(TAG,
- "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
- e);
- }
-
- return null;
+ return AidlUtils.buildProfilePathForPrimaryRef(mPkgState.getPackageName(), profileName);
}
- private void cleanupCurProfiles(@NonNull PackageState pkgState,
- @NonNull DetailedPrimaryDexInfo dexInfo) throws RemoteException {
- for (ProfilePath profile : getCurProfiles(pkgState, dexInfo)) {
- mInjector.getArtd().deleteProfile(profile);
- }
+ @Override
+ protected boolean isAppImageAllowed() {
+ // Disable app images if the app requests for the splits to be loaded in isolation because
+ // app images are unsupported for multiple class loaders (b/72696798).
+ return !PrimaryDexUtils.isIsolatedSplitLoading(mPkg);
}
+ @Override
@NonNull
- private List<ProfilePath> getCurProfiles(
- @NonNull PackageState pkgState, @NonNull DetailedPrimaryDexInfo dexInfo) {
+ protected OutputProfile buildOutputProfile(
+ @NonNull DetailedPrimaryDexInfo dexInfo, boolean isPublic) {
+ String profileName = getProfileName(dexInfo.splitName());
+ return AidlUtils.buildOutputProfileForPrimary(mPkgState.getPackageName(), profileName,
+ mPkgState.getAppId(), mSharedGid, isPublic);
+ }
+
+ @Override
+ @NonNull
+ protected List<ProfilePath> getCurProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) {
List<ProfilePath> profiles = new ArrayList<>();
for (UserHandle handle :
mInjector.getUserManager().getUserHandles(true /* excludeDying */)) {
int userId = handle.getIdentifier();
- PackageUserState userState = pkgState.getStateForUser(handle);
+ PackageUserState userState = mPkgState.getStateForUser(handle);
if (userState.isInstalled()) {
profiles.add(AidlUtils.buildProfilePathForPrimaryCur(
- userId, pkgState.getPackageName(), getProfileName(dexInfo.splitName())));
+ userId, mPkgState.getPackageName(), getProfileName(dexInfo.splitName())));
}
}
return profiles;
}
- @AutoValue
- abstract static class DexoptTarget {
- abstract @NonNull DetailedPrimaryDexInfo dexInfo();
- abstract @NonNull String isa();
- abstract boolean isInDalvikCache();
- abstract @NonNull String compilerFilter();
-
- static Builder builder() {
- return new AutoValue_PrimaryDexOptimizer_DexoptTarget.Builder();
- }
-
- @AutoValue.Builder
- abstract static class Builder {
- abstract Builder setDexInfo(@NonNull DetailedPrimaryDexInfo value);
- abstract Builder setIsa(@NonNull String value);
- abstract Builder setIsInDalvikCache(boolean value);
- abstract Builder setCompilerFilter(@NonNull String value);
- abstract DexoptTarget build();
- }
+ private boolean isSharedLibrary() {
+ // TODO(b/242688548): Package manager should provide a better API for this.
+ return !TextUtils.isEmpty(mPkg.getSdkLibraryName())
+ || !TextUtils.isEmpty(mPkg.getStaticSharedLibraryName())
+ || !mPkg.getLibraryNames().isEmpty();
}
- @AutoValue
- abstract static class GetDexoptNeededOptions {
- abstract @OptimizeFlags int flags();
- abstract boolean profileMerged();
- abstract boolean needsToBePublic();
-
- static Builder builder() {
- return new AutoValue_PrimaryDexOptimizer_GetDexoptNeededOptions.Builder();
- }
-
- @AutoValue.Builder
- abstract static class Builder {
- abstract Builder setFlags(@OptimizeFlags int value);
- abstract Builder setProfileMerged(boolean value);
- abstract Builder setNeedsToBePublic(boolean value);
- abstract GetDexoptNeededOptions build();
- }
- }
-
- /**
- * Injector pattern for testing purpose.
- *
- * @hide
- */
- @VisibleForTesting
- public static class Injector {
- @NonNull private final Context mContext;
-
- Injector(@NonNull Context context) {
- mContext = context;
- }
-
- boolean isSystemUiPackage(@NonNull String packageName) {
- return packageName.equals(mContext.getString(R.string.config_systemUi));
- }
-
- @NonNull
- UserManager getUserManager() {
- return mContext.getSystemService(UserManager.class);
- }
-
- @NonNull
- DexUseManager getDexUseManager() {
- return DexUseManager.getInstance();
- }
-
- @NonNull
- public IArtd getArtd() {
- return Utils.getArtd();
- }
+ @NonNull
+ private String getProfileName(@Nullable String splitName) {
+ return splitName == null ? "primary" : splitName + ".split";
}
}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
index 91507ed..3ffaf54 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
@@ -21,6 +21,7 @@
import android.text.TextUtils;
import com.android.internal.annotations.Immutable;
+import com.android.server.art.model.DetailedDexInfo;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.AndroidPackageSplit;
import com.android.server.pm.pkg.PackageState;
@@ -302,11 +303,11 @@
* producing it requires {@link PackageState}.
*/
@Immutable
- public static class DetailedPrimaryDexInfo extends PrimaryDexInfo {
+ public static class DetailedPrimaryDexInfo extends PrimaryDexInfo implements DetailedDexInfo {
private final @Nullable String mClassLoaderContext;
- DetailedPrimaryDexInfo(@NonNull AndroidPackageSplit split,
- @Nullable String classLoaderContext) {
+ DetailedPrimaryDexInfo(
+ @NonNull AndroidPackageSplit split, @Nullable String classLoaderContext) {
super(split);
mClassLoaderContext = classLoaderContext;
}
diff --git a/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
new file mode 100644
index 0000000..0f9a20e
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
@@ -0,0 +1,37 @@
+/*
+ * 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 com.android.internal.annotations.Immutable;
+
+/**
+ * Detailed information about a dex file.
+ *
+ * @hide
+ */
+@Immutable
+public interface DetailedDexInfo {
+ /** The path to the dex file. */
+ @NonNull String dexPath();
+
+ /**
+ * A string describing the structure of the class loader that the dex file is loaded with.
+ */
+ @NonNull String classLoaderContext();
+}
diff --git a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
index a001e74..b8faf09 100644
--- a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
@@ -90,7 +90,6 @@
@Before
public void setUp() throws Exception {
- lenient().when(mInjector.getPrimaryDexOptimizer()).thenReturn(mPrimaryDexOptimizer);
lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAhm);
lenient().when(mInjector.getPowerManager()).thenReturn(mPowerManager);
@@ -105,14 +104,17 @@
mPkg = mPkgState.getAndroidPackage();
mCancellationSignal = new CancellationSignal();
+ lenient()
+ .when(mInjector.getPrimaryDexOptimizer(
+ same(mPkgState), same(mPkg), same(mParams), same(mCancellationSignal)))
+ .thenReturn(mPrimaryDexOptimizer);
+
mDexOptHelper = new DexOptHelper(mInjector);
}
@Test
public void testDexopt() throws Exception {
- when(mPrimaryDexOptimizer.dexopt(
- same(mPkgState), same(mPkg), same(mParams), same(mCancellationSignal)))
- .thenReturn(mPrimaryResults);
+ when(mPrimaryDexOptimizer.dexopt()).thenReturn(mPrimaryResults);
OptimizeResult result = mDexOptHelper.dexopt(
mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg, mParams,
@@ -130,7 +132,7 @@
InOrder inOrder = inOrder(mPrimaryDexOptimizer, mWakeLock);
inOrder.verify(mWakeLock).acquire(anyLong());
- inOrder.verify(mPrimaryDexOptimizer).dexopt(any(), any(), any(), any());
+ inOrder.verify(mPrimaryDexOptimizer).dexopt();
inOrder.verify(mWakeLock).release();
}
@@ -165,9 +167,7 @@
lenient().when(mAhm.isHibernatingGlobally(PKG_NAME)).thenReturn(true);
lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(false);
- when(mPrimaryDexOptimizer.dexopt(
- same(mPkgState), same(mPkg), same(mParams), same(mCancellationSignal)))
- .thenReturn(mPrimaryResults);
+ when(mPrimaryDexOptimizer.dexopt()).thenReturn(mPrimaryResults);
OptimizeResult result = mDexOptHelper.dexopt(
mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg, mParams,
@@ -179,9 +179,7 @@
@Test
public void testDexoptAlwaysReleasesWakeLock() throws Exception {
- when(mPrimaryDexOptimizer.dexopt(
- same(mPkgState), same(mPkg), same(mParams), same(mCancellationSignal)))
- .thenThrow(IllegalStateException.class);
+ when(mPrimaryDexOptimizer.dexopt()).thenThrow(IllegalStateException.class);
try {
mDexOptHelper.dexopt(mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg,
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
index 000858c..407fd61 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
@@ -70,6 +70,8 @@
private OptimizeParams mOptimizeParams;
+ private PrimaryDexOptimizer mPrimaryDexOptimizer;
+
@Parameter(0) public Params mParams;
@Parameters(name = "{0}")
@@ -187,6 +189,9 @@
.setFlags(mParams.mShouldDowngrade ? ArtFlags.FLAG_SHOULD_DOWNGRADE : 0,
ArtFlags.FLAG_SHOULD_DOWNGRADE)
.build();
+
+ mPrimaryDexOptimizer = new PrimaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
}
@Test
@@ -255,8 +260,7 @@
isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE),
deepEq(dexoptOptions), any());
- assertThat(
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal))
+ assertThat(mPrimaryDexOptimizer.dexopt())
.comparingElementsUsing(TestingUtils.<DexContainerFileOptimizeResult>deepEquality())
.containsExactly(
new DexContainerFileOptimizeResult("/data/app/foo/base.apk",
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
index 504be90..2faa59d 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -93,6 +93,8 @@
private final DexoptResult mDexoptResult =
createDexoptResult(false /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */);
+ private PrimaryDexOptimizer mPrimaryDexOptimizer;
+
private List<ProfilePath> mUsedProfiles;
@Before
@@ -116,6 +118,9 @@
.when(mArtd.createCancellationSignal())
.thenReturn(mock(IArtdCancellationSignal.class));
+ mPrimaryDexOptimizer = new PrimaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+
mUsedProfiles = new ArrayList<>();
}
@@ -163,7 +168,7 @@
AidlUtils.buildDexMetadataPath(mSplit0DexPath))),
anyInt(), any(), any());
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
}
@Test
@@ -176,7 +181,7 @@
makeProfileUsable(mPrebuiltProfile);
makeProfileUsable(mDmProfile);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
verify(mArtd).getDexoptNeeded(
eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
@@ -210,7 +215,7 @@
makeProfileUsable(mPrebuiltProfile);
makeProfileUsable(mDmProfile);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm64", mRefProfile);
checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm", mRefProfile);
@@ -225,7 +230,7 @@
makeProfileUsable(mPrebuiltProfile);
makeProfileUsable(mDmProfile);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
InOrder inOrder = inOrder(mArtd);
@@ -254,7 +259,7 @@
when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
.thenReturn(FileVisibility.OTHER_READABLE);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
InOrder inOrder = inOrder(mArtd);
@@ -295,7 +300,7 @@
when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
.thenReturn(FileVisibility.OTHER_READABLE);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
// It should still use "speed-profile", but with the existing reference profile only.
verify(mArtd).getDexoptNeeded(
@@ -316,7 +321,7 @@
makeProfileNotUsable(mPrebuiltProfile);
makeProfileUsable(mDmProfile);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
verify(mArtd).copyAndRewriteProfile(
deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
@@ -340,7 +345,7 @@
any()))
.thenThrow(ServiceSpecificException.class);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
verify(mArtd).deleteProfile(
deepEq(ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath)));
@@ -365,7 +370,7 @@
argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
.thenReturn(FileVisibility.NOT_OTHER_READABLE);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
verify(mArtd).copyAndRewriteProfile(
deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
@@ -403,7 +408,7 @@
argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
.thenReturn(FileVisibility.OTHER_READABLE);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
// It should use the default dexopt trigger.
verify(mArtd).getDexoptNeeded(
@@ -418,7 +423,7 @@
when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile)))
.thenReturn(FileVisibility.NOT_OTHER_READABLE);
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ mPrimaryDexOptimizer.dexopt();
verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"),
eq(mDefaultDexoptTrigger));
@@ -447,11 +452,10 @@
// The result should only contain one element: the result of the first file with
// OPTIMIZE_CANCELLED.
- assertThat(
- mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal)
- .stream()
- .map(DexContainerFileOptimizeResult::getStatus)
- .collect(Collectors.toList()))
+ assertThat(mPrimaryDexOptimizer.dexopt()
+ .stream()
+ .map(DexContainerFileOptimizeResult::getStatus)
+ .collect(Collectors.toList()))
.containsExactly(OptimizeResult.OPTIMIZE_CANCELLED);
// It shouldn't continue after being cancelled on the first file.
@@ -486,10 +490,8 @@
.cancel();
Future<List<DexContainerFileOptimizeResult>> results =
- Executors.newSingleThreadExecutor().submit(() -> {
- return mPrimaryDexOptimizer.dexopt(
- mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
- });
+ Executors.newSingleThreadExecutor().submit(
+ () -> { return mPrimaryDexOptimizer.dexopt(); });
assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
index c175640..9b272d9 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
@@ -65,8 +65,6 @@
protected PackageUserState mPkgUserStateInstalled;
protected CancellationSignal mCancellationSignal;
- protected PrimaryDexOptimizer mPrimaryDexOptimizer;
-
@Before
public void setUp() throws Exception {
lenient().when(mInjector.getArtd()).thenReturn(mArtd);
@@ -103,8 +101,6 @@
mPkgState = createPackageState();
mPkg = mPkgState.getAndroidPackage();
mCancellationSignal = new CancellationSignal();
-
- mPrimaryDexOptimizer = new PrimaryDexOptimizer(mInjector);
}
private AndroidPackage createPackage() {