ART services: optimize package - Implement profile-guided compilation.
This CL contains the implementation of profile-guided compilation using
existing profiles: reference profiles, prebuilt profiles, and dm files.
Merging profiles is not in the scope of this CL.
Bug: 229268202
Test: atest ArtServiceTests
Ignore-AOSP-First: ART Services.
Change-Id: I0984e8fc0c3475d05251f9521223ac80d6b769cd
diff --git a/artd/binder/com/android/server/art/ProfilePath.aidl b/artd/binder/com/android/server/art/ProfilePath.aidl
index 3846b06..e12d94a 100644
--- a/artd/binder/com/android/server/art/ProfilePath.aidl
+++ b/artd/binder/com/android/server/art/ProfilePath.aidl
@@ -44,9 +44,14 @@
@utf8InCpp String id;
}
- /** Represents a profile built in the system image. */
+ /**
+ * Represents a profile next to a dex file. This is usually a prebuilt profile in the system
+ * image, but it can also be a profile that package manager can potentially put along with the
+ * APK during installation. The latter one is not officially supported by package manager, but
+ * OEMs can customize package manager to support that.
+ */
parcelable PrebuiltProfilePath {
- /** The path to the dex file that the prebuilt profile is next to. */
+ /** The path to the dex file that the profile is next to. */
@utf8InCpp String dexPath;
}
}
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index 200f3c9..7753144 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -81,11 +81,17 @@
srcs: [
"java/**/*.java",
],
+ libs: [
+ "auto_value_annotations",
+ ],
static_libs: [
"artd-aidl-java",
"modules-utils-shell-command-handler",
],
- plugins: ["java_api_finder"],
+ plugins: [
+ "auto_value_plugin",
+ "java_api_finder",
+ ],
jarjar_rules: "jarjar-rules.txt",
}
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
index 447c3d6..b28e1ff 100644
--- a/libartservice/service/java/com/android/server/art/AidlUtils.java
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -18,6 +18,9 @@
import static com.android.server.art.OutputArtifacts.PermissionSettings;
import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.ProfilePath.PrebuiltProfilePath;
+import static com.android.server.art.ProfilePath.RefProfilePath;
+import static com.android.server.art.ProfilePath.TmpRefProfilePath;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -77,4 +80,42 @@
outputArtifacts.permissionSettings = permissionSettings;
return outputArtifacts;
}
+
+ @NonNull
+ public static RefProfilePath buildRefProfilePath(
+ @NonNull String packageName, @NonNull String profileName) {
+ var refProfilePath = new RefProfilePath();
+ refProfilePath.packageName = packageName;
+ refProfilePath.profileName = profileName;
+ return refProfilePath;
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForRef(
+ @NonNull String packageName, @NonNull String profileName) {
+ return ProfilePath.refProfilePath(buildRefProfilePath(packageName, profileName));
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForPrebuilt(@NonNull String dexPath) {
+ var prebuiltProfilePath = new PrebuiltProfilePath();
+ prebuiltProfilePath.dexPath = dexPath;
+ return ProfilePath.prebuiltProfilePath(prebuiltProfilePath);
+ }
+
+ @NonNull
+ public static ProfilePath buildProfilePathForDm(@NonNull String dexPath) {
+ return ProfilePath.dexMetadataPath(buildDexMetadataPath(dexPath));
+ }
+
+ @NonNull
+ public static OutputProfile buildOutputProfile(@NonNull String packageName,
+ @NonNull String profileName, int uid, int gid, boolean isPublic) {
+ var outputProfile = new OutputProfile();
+ outputProfile.profilePath = new TmpRefProfilePath();
+ outputProfile.profilePath.refProfilePath = buildRefProfilePath(packageName, profileName);
+ outputProfile.profilePath.id = ""; // Will be filled by artd.
+ outputProfile.fsPermission = buildFsPermission(uid, gid, isPublic);
+ return outputProfile;
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
index 7d51df2..a63e4e6 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
@@ -20,6 +20,9 @@
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.RefProfilePath;
+import static com.android.server.art.ProfilePath.TmpRefProfilePath;
+import static com.android.server.art.model.ArtFlags.OptimizeFlags;
import static com.android.server.art.model.OptimizeResult.DexFileOptimizeResult;
import android.R;
@@ -31,6 +34,7 @@
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +44,8 @@
import com.android.server.art.wrapper.AndroidPackageApi;
import com.android.server.art.wrapper.PackageState;
+import com.google.auto.value.AutoValue;
+
import dalvik.system.DexFile;
import java.util.ArrayList;
@@ -69,6 +75,18 @@
@NonNull AndroidPackageApi pkg, @NonNull OptimizeParams params) throws RemoteException {
List<DexFileOptimizeResult> results = new ArrayList<>();
+ int uid = pkg.getUid();
+ if (uid < 0) {
+ throw new IllegalStateException(
+ "Package '" + pkgState.getPackageName() + "' has invalid app uid");
+ }
+ int sharedGid = UserHandle.getSharedAppGid(uid);
+ if (sharedGid < 0) {
+ throw new IllegalStateException(
+ String.format("Unable to get shared gid for package '%s' (uid: %d)",
+ pkgState.getPackageName(), uid));
+ }
+
String targetCompilerFilter =
adjustCompilerFilter(pkgState, pkg, params.getCompilerFilter(), params.getReason());
if (targetCompilerFilter.equals(OptimizeParams.COMPILER_FILTER_NOOP)) {
@@ -78,6 +96,8 @@
boolean isInDalvikCache = Utils.isInDalvikCache(pkgState);
for (DetailedPrimaryDexInfo dexInfo : PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+ OutputProfile profile = null;
+ boolean succeeded = true;
try {
if (!dexInfo.hasCode()) {
continue;
@@ -87,32 +107,69 @@
String compilerFilter = targetCompilerFilter;
+ boolean needsToBeShared = isSharedLibrary(pkg)
+ || mInjector.isUsedByOtherApps(pkgState.getPackageName());
+ // If true, implies that the profile has changed since the last compilation.
+ boolean profileMerged = false;
if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) {
- throw new UnsupportedOperationException(
- "Profile-guided compilation is not implemented");
+ if (needsToBeShared) {
+ profile = initReferenceProfile(pkgState, dexInfo, uid, sharedGid);
+ } else {
+ profile = copyOrInitReferenceProfile(pkgState, dexInfo, uid, sharedGid);
+ // TODO(jiakaiz): Merge profiles.
+ }
+ 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";
+ }
}
- PermissionSettings permissionSettings =
- getPermissionSettings(pkgState, pkg, true /* canBePublic */);
+ boolean isProfileGuidedCompilerFilter =
+ DexFile.isProfileGuidedCompilerFilter(compilerFilter);
+ assert isProfileGuidedCompilerFilter == (profile != null);
- DexoptOptions dexoptOptions = getDexoptOptions(pkgState, pkg, params);
+ boolean canBePublic =
+ !isProfileGuidedCompilerFilter || profile.fsPermission.isOtherReadable;
+ assert Utils.implies(needsToBeShared, canBePublic);
+ PermissionSettings permissionSettings =
+ getPermissionSettings(sharedGid, canBePublic);
+
+ DexoptOptions dexoptOptions =
+ getDexoptOptions(pkgState, pkg, params, isProfileGuidedCompilerFilter);
for (String isa : Utils.getAllIsas(pkgState)) {
@OptimizeResult.OptimizeStatus int status = OptimizeResult.OPTIMIZE_SKIPPED;
try {
+ DexoptTarget target = DexoptTarget.builder()
+ .setDexInfo(dexInfo)
+ .setIsa(isa)
+ .setIsInDalvikCache(isInDalvikCache)
+ .setCompilerFilter(compilerFilter)
+ .build();
+ GetDexoptNeededOptions options =
+ GetDexoptNeededOptions.builder()
+ .setProfileMerged(profileMerged)
+ .setFlags(params.getFlags())
+ .setNeedsToBePublic(needsToBeShared)
+ .build();
+
GetDexoptNeededResult getDexoptNeededResult =
- getDexoptNeeded(dexInfo, isa, compilerFilter,
- (params.getFlags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0,
- (params.getFlags() & ArtFlags.FLAG_FORCE) != 0);
+ getDexoptNeeded(target, options);
if (!getDexoptNeededResult.isDexoptNeeded) {
continue;
}
- ProfilePath inputProfile = null;
+ ProfilePath inputProfile = profile != null
+ ? ProfilePath.tmpRefProfilePath(profile.profilePath)
+ : null;
- status = dexoptFile(dexInfo, isa, isInDalvikCache, compilerFilter,
- inputProfile, getDexoptNeededResult, permissionSettings,
- params.getPriorityClass(), dexoptOptions);
+ status = dexoptFile(target, inputProfile, getDexoptNeededResult,
+ permissionSettings, params.getPriorityClass(), dexoptOptions);
} catch (ServiceSpecificException e) {
// Log the error and continue.
Log.e(TAG,
@@ -125,10 +182,25 @@
} finally {
results.add(new DexFileOptimizeResult(
dexInfo.dexPath(), isa, compilerFilter, status));
+ if (status != OptimizeResult.OPTIMIZE_SKIPPED
+ && status != OptimizeResult.OPTIMIZE_PERFORMED) {
+ succeeded = false;
+ }
}
}
+
+ if (profile != null && succeeded) {
+ // Commit the profile only if dexopt succeeds.
+ if (commitProfileChanges(profile)) {
+ profile = null;
+ }
+ // TODO(jiakaiz): If profileMerged is true, clear current profiles.
+ }
} finally {
- // TODO(jiakaiz): Cleanup profile.
+ if (profile != null) {
+ mInjector.getArtd().deleteProfile(
+ ProfilePath.tmpRefProfilePath(profile.profilePath));
+ }
}
}
@@ -170,21 +242,96 @@
return compilerFilter;
}
- @NonNull
- PermissionSettings getPermissionSettings(
- @NonNull PackageState pkgState, @NonNull AndroidPackageApi pkg, boolean canBePublic) {
- int uid = pkg.getUid();
- if (uid < 0) {
- throw new IllegalStateException(
- "Package '" + pkgState.getPackageName() + "' has invalid app uid");
- }
- int sharedGid = UserHandle.getSharedAppGid(uid);
- if (sharedGid < 0) {
- throw new IllegalStateException(
- String.format("Unable to get shared gid for package '%s' (uid: %d)",
- pkgState.getPackageName(), uid));
+ boolean isSharedLibrary(@NonNull AndroidPackageApi pkg) {
+ // TODO(b/242688548): Package manager should provide a better API for this.
+ return !TextUtils.isEmpty(pkg.getSdkLibName())
+ || !TextUtils.isEmpty(pkg.getStaticSharedLibName())
+ || !pkg.getLibraryNames().isEmpty();
+ }
+
+ /**
+ * Returns a reference profile initialized from a prebuilt profile or a DM profile if exists, or
+ * null otherwise.
+ */
+ @Nullable
+ private OutputProfile initReferenceProfile(@NonNull PackageState pkgState,
+ @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid) throws RemoteException {
+ String profileName = getProfileName(dexInfo.splitName());
+ OutputProfile output = AidlUtils.buildOutputProfile(
+ pkgState.getPackageName(), profileName, uid, gid, true /* isPublic */);
+
+ ProfilePath prebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath());
+ try {
+ // If the APK is really a prebuilt one, rewriting the profile is unnecessary because the
+ // dex location is known at build time and is correctly set in the profile header.
+ // However, the APK can also be an installed one, in which case partners may place a
+ // profile file next to the APK at install time. Rewriting the profile in the latter
+ // case is necessary.
+ if (mInjector.getArtd().copyAndRewriteProfile(
+ prebuiltProfile, output, dexInfo.dexPath())) {
+ return output;
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG,
+ String.format(
+ "Failed to use prebuilt profile [packageName = %s, profileName = %s]",
+ pkgState.getPackageName(), profileName),
+ e);
}
+ ProfilePath dmProfile = AidlUtils.buildProfilePathForDm(dexInfo.dexPath());
+ try {
+ if (mInjector.getArtd().copyAndRewriteProfile(dmProfile, output, dexInfo.dexPath())) {
+ return output;
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG,
+ String.format("Failed to use profile in dex metadata file "
+ + "[packageName = %s, profileName = %s]",
+ pkgState.getPackageName(), profileName),
+ e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Copies the existing reference profile if exists, or initializes a reference profile
+ * otherwise.
+ */
+ @Nullable
+ private OutputProfile copyOrInitReferenceProfile(@NonNull PackageState pkgState,
+ @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid) throws RemoteException {
+ String profileName = getProfileName(dexInfo.splitName());
+ ProfilePath refProfile =
+ AidlUtils.buildProfilePathForRef(pkgState.getPackageName(), profileName);
+ try {
+ if (mInjector.getArtd().isProfileUsable(refProfile, dexInfo.dexPath())) {
+ boolean isOtherReadable = mInjector.getArtd().getProfileVisibility(refProfile)
+ == FileVisibility.OTHER_READABLE;
+ OutputProfile output = AidlUtils.buildOutputProfile(pkgState.getPackageName(),
+ getProfileName(dexInfo.splitName()), uid, gid, isOtherReadable);
+ mInjector.getArtd().copyProfile(refProfile, output);
+ return output;
+ }
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG,
+ String.format("Failed to use the existing reference profile "
+ + "[packageName = %s, profileName = %s]",
+ pkgState.getPackageName(), profileName),
+ e);
+ }
+
+ return initReferenceProfile(pkgState, dexInfo, uid, gid);
+ }
+
+ @NonNull
+ public String getProfileName(@Nullable String splitName) {
+ return splitName == null ? "primary" : splitName + ".split";
+ }
+
+ @NonNull
+ PermissionSettings getPermissionSettings(int sharedGid, 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
@@ -200,12 +347,18 @@
@NonNull
private DexoptOptions getDexoptOptions(@NonNull PackageState pkgState,
- @NonNull AndroidPackageApi pkg, @NonNull OptimizeParams params) {
+ @NonNull AndroidPackageApi pkg, @NonNull OptimizeParams params,
+ boolean isProfileGuidedFilter) {
DexoptOptions dexoptOptions = new DexoptOptions();
dexoptOptions.compilationReason = params.getReason();
dexoptOptions.targetSdkVersion = pkg.getTargetSdkVersion();
dexoptOptions.debuggable = pkg.isDebuggable() || isAlwaysDebuggable();
- dexoptOptions.generateAppImage = false;
+ // 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;
}
@@ -231,47 +384,66 @@
}
@NonNull
- GetDexoptNeededResult getDexoptNeeded(@NonNull DetailedPrimaryDexInfo dexInfo,
- @NonNull String isa, @NonNull String compilerFilter, boolean shouldDowngrade,
- boolean force) throws RemoteException {
- int dexoptTrigger = getDexoptTrigger(shouldDowngrade, force);
+ 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(dexInfo.dexPath(), isa,
- dexInfo.classLoaderContext(), compilerFilter, dexoptTrigger);
+ GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
+ target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+ target.compilerFilter(), dexoptTrigger);
return result;
}
- int getDexoptTrigger(boolean shouldDowngrade, boolean force) {
- if (force) {
+ 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 (shouldDowngrade) {
+ if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
return DexoptTrigger.COMPILER_FILTER_IS_WORSE;
}
- return DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ 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 @OptimizeResult.OptimizeStatus int dexoptFile(@NonNull DetailedPrimaryDexInfo dexInfo,
- @NonNull String isa, boolean isInDalvikCache, @NonNull String compilerFilter,
+ private @OptimizeResult.OptimizeStatus int dexoptFile(@NonNull DexoptTarget target,
@Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
@NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
@NonNull DexoptOptions dexoptOptions) throws RemoteException {
- OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
- dexInfo.dexPath(), isa, isInDalvikCache, permissionSettings);
+ OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(),
+ target.isa(), target.isInDalvikCache(), permissionSettings);
- VdexPath inputVdex = getInputVdex(getDexoptNeededResult, dexInfo.dexPath(), isa);
+ VdexPath inputVdex =
+ getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
- if (!mInjector.getArtd().dexopt(outputArtifacts, dexInfo.dexPath(), isa,
- dexInfo.classLoaderContext(), compilerFilter, profile, inputVdex, priorityClass,
- dexoptOptions)) {
+ if (!mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
+ target.dexInfo().classLoaderContext(), target.compilerFilter(), profile,
+ inputVdex, priorityClass, dexoptOptions)) {
return OptimizeResult.OPTIMIZE_CANCELLED;
}
@@ -300,6 +472,61 @@
}
}
+ boolean commitProfileChanges(@NonNull OutputProfile profile) throws RemoteException {
+ try {
+ mInjector.getArtd().commitTmpProfile(profile.profilePath);
+ return true;
+ } catch (ServiceSpecificException e) {
+ RefProfilePath refProfilePath = profile.profilePath.refProfilePath;
+ Log.e(TAG,
+ String.format(
+ "Failed to commit profile changes [packageName = %s, profileName = %s]",
+ refProfilePath.packageName, refProfilePath.profileName),
+ e);
+ return false;
+ }
+ }
+
+ @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();
+ }
+ }
+
+ @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.
*
@@ -317,6 +544,11 @@
return packageName.equals(mContext.getString(R.string.config_systemUi));
}
+ boolean isUsedByOtherApps(@NonNull String packageName) {
+ // TODO(jiakaiz): Get the real value.
+ return false;
+ }
+
@NonNull
public IArtd getArtd() {
return Utils.getArtd();
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
index d6b8a59..81ec165 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java
@@ -107,8 +107,7 @@
String[] splitClassLoaderNames = pkg.getSplitClassLoaderNames();
SparseArray<int[]> splitDependencies = pkg.getSplitDependencies();
- boolean isIsolatedSplitLoading =
- pkg.isIsolatedSplitLoading() && !Utils.isEmpty(splitDependencies);
+ boolean isIsolatedSplitLoading = isIsolatedSplitLoading(pkg);
for (int i = 1; i < dexInfos.size(); i++) {
assert dexInfos.get(i).mSplitIndex == i - 1;
@@ -277,6 +276,10 @@
.collect(Collectors.joining("#", "{", "}"));
}
+ public static boolean isIsolatedSplitLoading(@NonNull AndroidPackageApi pkg) {
+ return pkg.isIsolatedSplitLoading() && !Utils.isEmpty(pkg.getSplitDependencies());
+ }
+
/** Basic information about a primary dex file (either the base APK or a split APK). */
@Immutable
public static class PrimaryDexInfo {
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 9e44760..8c1cfbe 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -91,4 +91,8 @@
}
return artd;
}
+
+ public static boolean implies(boolean cond1, boolean cond2) {
+ return cond1 ? cond2 : true;
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/AndroidPackageApi.java b/libartservice/service/java/com/android/server/art/wrapper/AndroidPackageApi.java
index a6431b0..fac534a 100644
--- a/libartservice/service/java/com/android/server/art/wrapper/AndroidPackageApi.java
+++ b/libartservice/service/java/com/android/server/art/wrapper/AndroidPackageApi.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.util.SparseArray;
+import java.util.List;
+
/** @hide */
public class AndroidPackageApi {
private final Object mPkg;
@@ -160,4 +162,31 @@
throw new RuntimeException(e);
}
}
+
+ @Nullable
+ public String getSdkLibName() {
+ try {
+ return (String) mPkg.getClass().getMethod("getSdkLibName").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ public String getStaticSharedLibName() {
+ try {
+ return (String) mPkg.getClass().getMethod("getStaticSharedLibName").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ public List<String> getLibraryNames() {
+ try {
+ return (List<String>) mPkg.getClass().getMethod("getLibraryNames").invoke(mPkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
index c7c779e..43aac8d 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
@@ -192,7 +192,7 @@
PermissionSettings permissionSettings = buildPermissionSettings(
buildFsPermission(Process.SYSTEM_UID, Process.SYSTEM_UID,
false /* isOtherReadable */, true /* isOtherExecutable */),
- buildFsPermission(Process.SYSTEM_UID, 52345, true /* isOtherReadable */),
+ buildFsPermission(Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */),
null /* seContext */);
DexoptOptions dexoptOptions = new DexoptOptions();
dexoptOptions.compilationReason = "install";
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
index 8db3032..03151e7 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -19,31 +19,83 @@
import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
import static com.android.server.art.testing.TestingUtils.deepEq;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.ServiceSpecificException;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.testing.TestingUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+import java.util.ArrayList;
+import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PrimaryDexOptimizerTest extends PrimaryDexOptimizerTestBase {
- private OptimizeParams mOptimizeParams;
+ private final OptimizeParams mOptimizeParams =
+ new OptimizeParams.Builder("install").setCompilerFilter("speed-profile").build();
+
+ private final String mDexPath = "/data/app/foo/base.apk";
+ private final ProfilePath mRefProfile = AidlUtils.buildProfilePathForRef(PKG_NAME, "primary");
+ private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
+ private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
+ private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfile(
+ PKG_NAME, "primary", UID, SHARED_GID, true /* isOtherReadable */);
+ private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfile(
+ PKG_NAME, "primary", UID, SHARED_GID, false /* isOtherReadable */);
+
+ private final String mSplit0DexPath = "/data/app/foo/split_0.apk";
+ private final ProfilePath mSplit0RefProfile =
+ AidlUtils.buildProfilePathForRef(PKG_NAME, "split_0.split");
+ private final OutputProfile mSplit0PrivateOutputProfile = AidlUtils.buildOutputProfile(
+ PKG_NAME, "split_0.split", UID, SHARED_GID, false /* isOtherReadable */);
+
+ private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ private final int mForceDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
+ | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
+
+ private List<ProfilePath> mUsedProfiles;
@Before
public void setUp() throws Exception {
super.setUp();
- mOptimizeParams = new OptimizeParams.Builder("install").setCompilerFilter("verify").build();
+ // By default, none of the profiles are usable.
+ lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
+ lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false);
+
+ // Dexopt is by default needed and successful.
+ lenient()
+ .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
+ .thenReturn(dexoptIsNeeded());
+ lenient()
+ .when(mArtd.dexopt(
+ any(), any(), any(), any(), any(), any(), any(), anyInt(), any()))
+ .thenReturn(true);
+
+ mUsedProfiles = new ArrayList<>();
}
@Test
@@ -51,42 +103,284 @@
// null.
doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR))
.when(mArtd)
- .getDexoptNeeded(eq("/data/app/foo/base.apk"), eq("arm64"), any(), any(), anyInt());
- doReturn(true).when(mArtd).dexopt(any(), eq("/data/app/foo/base.apk"), eq("arm64"), any(),
- any(), any(), isNull(), anyInt(), any());
+ .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt());
+ doReturn(true).when(mArtd).dexopt(
+ any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), anyInt(), any());
// ArtifactsPath, isInDalvikCache=true.
doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE))
.when(mArtd)
- .getDexoptNeeded(eq("/data/app/foo/base.apk"), eq("arm"), any(), any(), anyInt());
- doReturn(true).when(mArtd).dexopt(any(), eq("/data/app/foo/base.apk"), eq("arm"), any(),
- any(), any(),
- deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
- "/data/app/foo/base.apk", "arm", true /* isInDalvikCache */))),
+ .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt());
+ doReturn(true).when(mArtd).dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
+ deepEq(VdexPath.artifactsPath(
+ AidlUtils.buildArtifactsPath(mDexPath, "arm", true /* isInDalvikCache */))),
anyInt(), any());
// ArtifactsPath, isInDalvikCache=false.
doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX))
.when(mArtd)
- .getDexoptNeeded(
- eq("/data/app/foo/split_0.apk"), eq("arm64"), any(), any(), anyInt());
- doReturn(true).when(mArtd).dexopt(any(), eq("/data/app/foo/split_0.apk"), eq("arm64"),
- any(), any(), any(),
+ .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt());
+ doReturn(true).when(mArtd).dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(),
+ any(),
deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
- "/data/app/foo/split_0.apk", "arm64", false /* isInDalvikCache */))),
+ mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
anyInt(), any());
// DexMetadataPath.
doReturn(dexoptIsNeeded(ArtifactsLocation.DM))
.when(mArtd)
- .getDexoptNeeded(
- eq("/data/app/foo/split_0.apk"), eq("arm"), any(), any(), anyInt());
- doReturn(true).when(mArtd).dexopt(any(), eq("/data/app/foo/split_0.apk"), eq("arm"), any(),
- any(), any(),
- deepEq(VdexPath.dexMetadataPath(
- AidlUtils.buildDexMetadataPath("/data/app/foo/split_0.apk"))),
+ .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt());
+ doReturn(true).when(mArtd).dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(),
+ deepEq(VdexPath.dexMetadataPath(AidlUtils.buildDexMetadataPath(mSplit0DexPath))),
anyInt(), any());
mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
}
+
+ @Test
+ public void testDexoptUsesRefProfile() throws Exception {
+ makeProfileUsable(mRefProfile);
+ when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ // Other profiles are also usable, but they shouldn't be used.
+ makeProfileUsable(mPrebuiltProfile);
+ makeProfileUsable(mDmProfile);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ InOrder inOrder = inOrder(mArtd);
+
+ inOrder.verify(mArtd).copyProfile(deepEq(mRefProfile), deepEq(mPrivateOutputProfile));
+
+ inOrder.verify(mArtd).getDexoptNeeded(
+ eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithPrivateProfile(
+ inOrder.verify(mArtd), mDexPath, "arm64", mPrivateOutputProfile);
+
+ inOrder.verify(mArtd).getDexoptNeeded(
+ eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithPrivateProfile(
+ inOrder.verify(mArtd), mDexPath, "arm", mPrivateOutputProfile);
+
+ inOrder.verify(mArtd).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath));
+
+ // There is no profile for split 0, so it should fall back to "verify".
+ inOrder.verify(mArtd).getDexoptNeeded(
+ eq(mSplit0DexPath), eq("arm64"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(inOrder.verify(mArtd), mSplit0DexPath, "arm64", "verify");
+
+ inOrder.verify(mArtd).getDexoptNeeded(
+ eq(mSplit0DexPath), eq("arm"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(inOrder.verify(mArtd), mSplit0DexPath, "arm", "verify");
+
+ verifyProfileNotUsed(mPrebuiltProfile);
+ verifyProfileNotUsed(mDmProfile);
+ }
+
+ @Test
+ public void testDexoptUsesPublicRefProfile() throws Exception {
+ // The ref profile is usable and public.
+ makeProfileUsable(mRefProfile);
+ when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
+ // Other profiles are also usable, but they shouldn't be used.
+ makeProfileUsable(mPrebuiltProfile);
+ makeProfileUsable(mDmProfile);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ verify(mArtd).copyProfile(deepEq(mRefProfile), deepEq(mPublicOutputProfile));
+
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm64", mPublicOutputProfile);
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm", mPublicOutputProfile);
+
+ verifyProfileNotUsed(mPrebuiltProfile);
+ verifyProfileNotUsed(mDmProfile);
+ }
+
+ @Test
+ public void testDexoptUsesPrebuiltProfile() throws Exception {
+ makeProfileNotUsable(mRefProfile);
+ makeProfileUsable(mPrebuiltProfile);
+ makeProfileUsable(mDmProfile);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ verify(mArtd).copyAndRewriteProfile(
+ deepEq(mPrebuiltProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm64", mPublicOutputProfile);
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm", mPublicOutputProfile);
+
+ verifyProfileNotUsed(mRefProfile);
+ verifyProfileNotUsed(mDmProfile);
+ }
+
+ @Test
+ public void testDexoptUsesDmProfile() throws Exception {
+ makeProfileNotUsable(mRefProfile);
+ makeProfileNotUsable(mPrebuiltProfile);
+ makeProfileUsable(mDmProfile);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ verify(mArtd).copyAndRewriteProfile(
+ deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm64", mPublicOutputProfile);
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm", mPublicOutputProfile);
+
+ verifyProfileNotUsed(mRefProfile);
+ verifyProfileNotUsed(mPrebuiltProfile);
+ }
+
+ @Test
+ public void testDexoptDeletesProfileOnFailure() throws Exception {
+ makeProfileUsable(mRefProfile);
+ when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), anyInt(), any()))
+ .thenThrow(ServiceSpecificException.class);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ verify(mArtd).deleteProfile(
+ deepEq(ProfilePath.tmpRefProfilePath(mPrivateOutputProfile.profilePath)));
+ verify(mArtd, never()).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath));
+ }
+
+ @Test
+ public void testDexoptNeedsToBeShared() throws Exception {
+ when(mInjector.isUsedByOtherApps(PKG_NAME)).thenReturn(true);
+
+ // The ref profile is usable but shouldn't be used.
+ makeProfileUsable(mRefProfile);
+
+ makeProfileNotUsable(mPrebuiltProfile);
+ makeProfileUsable(mDmProfile);
+
+ // The existing artifacts are private.
+ when(mArtd.getArtifactsVisibility(
+ argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ verify(mArtd).copyAndRewriteProfile(
+ deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
+
+ // It should re-compile anyway.
+ verify(mArtd).getDexoptNeeded(
+ eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm64", mPublicOutputProfile);
+
+ verify(mArtd).getDexoptNeeded(
+ eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
+ checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm", mPublicOutputProfile);
+
+ checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "speed");
+ checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "speed");
+
+ verifyProfileNotUsed(mRefProfile);
+ verifyProfileNotUsed(mPrebuiltProfile);
+ }
+
+ @Test
+ public void testDexoptNeedsToBeSharedArtifactsArePublic() throws Exception {
+ // Same setup as above, but the existing artifacts are public.
+ when(mInjector.isUsedByOtherApps(PKG_NAME)).thenReturn(true);
+ makeProfileUsable(mRefProfile);
+ makeProfileNotUsable(mPrebuiltProfile);
+ makeProfileUsable(mDmProfile);
+ when(mArtd.getArtifactsVisibility(
+ argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ // It should use the default dexopt trigger.
+ verify(mArtd).getDexoptNeeded(
+ eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+ verify(mArtd).getDexoptNeeded(
+ eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
+ }
+
+ @Test
+ public void testDexoptUsesProfileForSplit() throws Exception {
+ makeProfileUsable(mSplit0RefProfile);
+ when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile)))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
+
+ verify(mArtd).copyProfile(deepEq(mSplit0RefProfile), deepEq(mSplit0PrivateOutputProfile));
+
+ verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"),
+ eq(mDefaultDexoptTrigger));
+ checkDexoptWithPrivateProfile(
+ verify(mArtd), mSplit0DexPath, "arm64", mSplit0PrivateOutputProfile);
+
+ verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), eq("speed-profile"),
+ eq(mDefaultDexoptTrigger));
+ checkDexoptWithPrivateProfile(
+ verify(mArtd), mSplit0DexPath, "arm", mSplit0PrivateOutputProfile);
+ }
+
+ private void checkDexoptWithPublicProfile(
+ IArtd artd, String dexPath, String isa, OutputProfile profile) throws Exception {
+ artd.dexopt(
+ argThat(artifacts
+ -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
+ eq(dexPath), eq(isa), any(), eq("speed-profile"),
+ deepEq(ProfilePath.tmpRefProfilePath(profile.profilePath)), any(), anyInt(),
+ argThat(dexoptOptions -> dexoptOptions.generateAppImage == true));
+ }
+
+ private void checkDexoptWithPrivateProfile(
+ IArtd artd, String dexPath, String isa, OutputProfile profile) throws Exception {
+ artd.dexopt(
+ argThat(artifacts
+ -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == false),
+ eq(dexPath), eq(isa), any(), eq("speed-profile"),
+ deepEq(ProfilePath.tmpRefProfilePath(profile.profilePath)), any(), anyInt(),
+ argThat(dexoptOptions -> dexoptOptions.generateAppImage == true));
+ }
+
+ private void checkDexoptWithNoProfile(
+ IArtd artd, String dexPath, String isa, String compilerFilter) throws Exception {
+ artd.dexopt(
+ argThat(artifacts
+ -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
+ eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), anyInt(),
+ argThat(dexoptOptions -> dexoptOptions.generateAppImage == false));
+ }
+
+ private void verifyProfileNotUsed(ProfilePath profile) throws Exception {
+ assertThat(mUsedProfiles)
+ .comparingElementsUsing(TestingUtils.<ProfilePath>deepEquality())
+ .doesNotContain(profile);
+ }
+
+ private void makeProfileUsable(ProfilePath profile) throws Exception {
+ lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenAnswer(invocation -> {
+ mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
+ return true;
+ });
+ lenient()
+ .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
+ .thenAnswer(invocation -> {
+ mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
+ return true;
+ });
+ }
+
+ private void makeProfileNotUsable(ProfilePath profile) throws Exception {
+ lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenReturn(false);
+ lenient()
+ .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
+ .thenReturn(false);
+ }
}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
index 92b4cb2..2f2c33f 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
@@ -26,6 +26,7 @@
import android.content.pm.ApplicationInfo;
import android.os.SystemProperties;
+import android.os.UserHandle;
import com.android.server.art.testing.StaticMockitoRule;
import com.android.server.art.wrapper.AndroidPackageApi;
@@ -41,6 +42,8 @@
public class PrimaryDexOptimizerTestBase {
protected static final String PKG_NAME = "com.example.foo";
+ protected static final int UID = 12345;
+ protected static final int SHARED_GID = UserHandle.getSharedAppGid(UID);
@Rule public StaticMockitoRule mockitoRule = new StaticMockitoRule(SystemProperties.class);
@@ -55,6 +58,7 @@
public void setUp() throws Exception {
lenient().when(mInjector.getArtd()).thenReturn(mArtd);
lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+ lenient().when(mInjector.isUsedByOtherApps(any())).thenReturn(false);
lenient()
.when(SystemProperties.get("dalvik.vm.systemuicompilerfilter"))
@@ -62,6 +66,8 @@
lenient()
.when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
.thenReturn(false);
+ lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
+ lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
mPkgState = createPackageState();
mPkg = mPkgState.getAndroidPackage();
@@ -83,12 +89,15 @@
lenient()
.when(pkg.getSplitFlags())
.thenReturn(new int[] {ApplicationInfo.FLAG_HAS_CODE, 0});
- lenient().when(pkg.getUid()).thenReturn(12345);
+ lenient().when(pkg.getUid()).thenReturn(UID);
lenient().when(pkg.isVmSafeMode()).thenReturn(false);
lenient().when(pkg.isDebuggable()).thenReturn(false);
lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
lenient().when(pkg.isUsesNonSdkApi()).thenReturn(false);
+ lenient().when(pkg.getSdkLibName()).thenReturn(null);
+ lenient().when(pkg.getStaticSharedLibName()).thenReturn(null);
+ lenient().when(pkg.getLibraryNames()).thenReturn(new ArrayList<>());
return pkg;
}
diff --git a/libartservice/service/javatests/com/android/server/art/UtilsTest.java b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
index da39eec..a167919 100644
--- a/libartservice/service/javatests/com/android/server/art/UtilsTest.java
+++ b/libartservice/service/javatests/com/android/server/art/UtilsTest.java
@@ -64,4 +64,12 @@
public void testArrayIsEmptyFalse() {
assertThat(Utils.isEmpty(new int[] {1})).isFalse();
}
+
+ @Test
+ public void testImplies() {
+ assertThat(Utils.implies(false, false)).isTrue();
+ assertThat(Utils.implies(false, true)).isTrue();
+ assertThat(Utils.implies(true, false)).isFalse();
+ assertThat(Utils.implies(true, true)).isTrue();
+ }
}