summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jiakai Zhang <jiakaiz@google.com> 2024-03-21 17:53:13 +0000
committer Jiakai Zhang <jiakaiz@google.com> 2024-04-03 14:34:51 +0100
commitcf654f0fe56c83110114e1eb82bac73faf1b07da (patch)
tree1ec39451fff29de0f55a3629f0d600cd52d47259
parent52a32e54464e10c525e25b749c591e09fe122f83 (diff)
Support disabling embedded profile through a dm file.
After this change, ART Service will look at `config.pb` in the dm file and check the `enable_embedded_profile` field to determine whether to allow using the the embedded profile (`assets/art-profile/baseline.prof`) in the apk. The field default to true. Bug: 330689353 Test: atest CtsCompilationTestCases Test: atest ArtServiceTests Change-Id: Ie763e7deaa3f283cf469980c857f0a23abfec5b9
-rw-r--r--libartservice/service/java/com/android/server/art/ArtManagerLocal.java10
-rw-r--r--libartservice/service/java/com/android/server/art/DexMetadataHelper.java115
-rw-r--r--libartservice/service/java/com/android/server/art/Dexopter.java60
-rw-r--r--libartservice/service/java/com/android/server/art/Utils.java17
-rw-r--r--libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java49
-rw-r--r--libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java5
-rw-r--r--libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java75
-rw-r--r--libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java4
-rw-r--r--libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java6
-rw-r--r--libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java15
-rw-r--r--libartservice/service/proto/dex_metadata_config.proto27
11 files changed, 341 insertions, 42 deletions
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index f7083309d3..f6fccee9e5 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -19,6 +19,7 @@ package com.android.server.art;
import static com.android.server.art.ArtFileManager.ProfileLists;
import static com.android.server.art.ArtFileManager.UsableArtifactLists;
import static com.android.server.art.ArtFileManager.WritableArtifactLists;
+import static com.android.server.art.DexMetadataHelper.DexMetadataInfo;
import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
import static com.android.server.art.ReasonMapping.BatchDexoptReason;
@@ -728,12 +729,15 @@ public final class ArtManagerLocal {
PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName);
+ DexMetadataPath dmPath = AidlUtils.buildDexMetadataPath(dexInfo.dexPath());
+ DexMetadataInfo dmInfo = mInjector.getDexMetadataHelper().getDexMetadataInfo(dmPath);
List<ProfilePath> profiles = new ArrayList<>();
InitProfileResult result = Utils.getOrInitReferenceProfile(mInjector.getArtd(),
dexInfo.dexPath(), PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo),
PrimaryDexUtils.getExternalProfiles(dexInfo),
+ dmInfo.config().getEnableEmbeddedProfile(),
PrimaryDexUtils.buildOutputProfile(pkgState, dexInfo, Process.SYSTEM_UID,
Process.SYSTEM_UID, false /* isPublic */));
if (!result.externalProfileErrors().isEmpty()) {
@@ -1526,5 +1530,11 @@ public final class ArtManagerLocal {
public ArtFileManager getArtFileManager() {
return new ArtFileManager(getContext());
}
+
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @NonNull
+ public DexMetadataHelper getDexMetadataHelper() {
+ return new DexMetadataHelper();
+ }
}
}
diff --git a/libartservice/service/java/com/android/server/art/DexMetadataHelper.java b/libartservice/service/java/com/android/server/art/DexMetadataHelper.java
new file mode 100644
index 0000000000..1252123479
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/DexMetadataHelper.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.proto.DexMetadataConfig;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.NoSuchFileException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * A helper class to handle dex metadata (dm) files.
+ *
+ * Note that this is not the only consumer of dm files. A dm file is a container that contains
+ * various types of files for various purposes, passed down to various layers and consumed by them.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class DexMetadataHelper {
+ private static final String TAG = ArtManagerLocal.TAG;
+
+ @NonNull private final Injector mInjector;
+
+ public DexMetadataHelper() {
+ this(new Injector());
+ }
+
+ @VisibleForTesting
+ public DexMetadataHelper(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ @NonNull
+ public DexMetadataInfo getDexMetadataInfo(@Nullable DexMetadataPath dmPath) {
+ if (dmPath == null) {
+ return getDefaultDexMetadataInfo();
+ }
+
+ String realDmPath = getDmPath(dmPath);
+ try (var zipFile = mInjector.openZipFile(realDmPath)) {
+ ZipEntry entry = zipFile.getEntry("config.pb");
+ if (entry == null) {
+ return new DexMetadataInfo(dmPath, DexMetadataConfig.getDefaultInstance());
+ }
+ try (InputStream stream = zipFile.getInputStream(entry)) {
+ return new DexMetadataInfo(dmPath, DexMetadataConfig.parseFrom(stream));
+ }
+ } catch (IOException e) {
+ if (!(e instanceof FileNotFoundException || e instanceof NoSuchFileException)) {
+ Log.e(TAG, String.format("Failed to read dm file '%s'", realDmPath), e);
+ }
+ return getDefaultDexMetadataInfo();
+ }
+ }
+
+ @NonNull
+ private DexMetadataInfo getDefaultDexMetadataInfo() {
+ return new DexMetadataInfo(null /* dmPath */, DexMetadataConfig.getDefaultInstance());
+ }
+
+ @NonNull
+ public static String getDmPath(@NonNull DexMetadataPath dmPath) {
+ String dexPath = dmPath.dexPath;
+ int pos = dexPath.lastIndexOf(".");
+ return (pos != -1 ? dexPath.substring(0, pos) : dexPath) + ".dm";
+ }
+
+ /**
+ * @param dmPath Represents the path to the dm file, if it exists. Or null if the file doesn't
+ * exist or an error occurred.
+ * @param config The config deserialized from `config.pb`, if it exists. Or the default instance
+ * if the file doesn't exist or an error occurred.
+ */
+ public record DexMetadataInfo(
+ @Nullable DexMetadataPath dmPath, @NonNull DexMetadataConfig config) {}
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ @NonNull
+ ZipFile openZipFile(@NonNull String path) throws IOException {
+ return new ZipFile(path);
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
index 5ff63b30e8..d8d6b3d5c6 100644
--- a/libartservice/service/java/com/android/server/art/Dexopter.java
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -17,6 +17,7 @@
package com.android.server.art;
import static com.android.server.art.ArtManagerLocal.AdjustCompilerFilterCallback;
+import static com.android.server.art.DexMetadataHelper.DexMetadataInfo;
import static com.android.server.art.OutputArtifacts.PermissionSettings;
import static com.android.server.art.ProfilePath.TmpProfilePath;
import static com.android.server.art.Utils.Abi;
@@ -123,18 +124,29 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
continue;
}
+ DexMetadataInfo dmInfo =
+ mInjector.getDexMetadataHelper().getDexMetadataInfo(buildDmPath(dexInfo));
+
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 (!dmInfo.config().getEnableEmbeddedProfile()) {
+ String dmPath = DexMetadataHelper.getDmPath(
+ Objects.requireNonNull(dmInfo.dmPath()));
+ Log.i(TAG, "Embedded profile disabled by config in the dm file " + dmPath);
+ }
+
if (needsToBeShared) {
- InitProfileResult result = initReferenceProfile(dexInfo);
+ InitProfileResult result = initReferenceProfile(
+ dexInfo, dmInfo.config().getEnableEmbeddedProfile());
profile = result.profile();
isOtherReadable = result.isOtherReadable();
externalProfileErrors = result.externalProfileErrors();
} else {
- InitProfileResult result = getOrInitReferenceProfile(dexInfo);
+ InitProfileResult result = getOrInitReferenceProfile(
+ dexInfo, dmInfo.config().getEnableEmbeddedProfile());
profile = result.profile();
isOtherReadable = result.isOtherReadable();
externalProfileErrors = result.externalProfileErrors();
@@ -183,6 +195,7 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
.setIsa(abi.isa())
.setIsInDalvikCache(isInDalvikCache)
.setCompilerFilter(compilerFilter)
+ .setDmPath(dmInfo.dmPath())
.build();
var options = GetDexoptNeededOptions.builder()
.setProfileMerged(profileMerged)
@@ -377,18 +390,19 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
/** @see Utils#getOrInitReferenceProfile */
@Nullable
- private InitProfileResult getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
- throws RemoteException {
+ private InitProfileResult getOrInitReferenceProfile(
+ @NonNull DexInfoType dexInfo, boolean enableEmbeddedProfile) throws RemoteException {
return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
- buildRefProfilePath(dexInfo), getExternalProfiles(dexInfo),
+ buildRefProfilePath(dexInfo), getExternalProfiles(dexInfo), enableEmbeddedProfile,
buildOutputProfile(dexInfo, true /* isPublic */));
}
@Nullable
- private InitProfileResult initReferenceProfile(@NonNull DexInfoType dexInfo)
- throws RemoteException {
+ private InitProfileResult initReferenceProfile(
+ @NonNull DexInfoType dexInfo, boolean enableEmbeddedProfile) throws RemoteException {
return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
- getExternalProfiles(dexInfo), buildOutputProfile(dexInfo, true /* isPublic */));
+ getExternalProfiles(dexInfo), enableEmbeddedProfile,
+ buildOutputProfile(dexInfo, true /* isPublic */));
}
@NonNull
@@ -485,8 +499,7 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
VdexPath inputVdex =
getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
- DexMetadataPath dmFile = getDmFile(target.dexInfo());
- if (dmFile != null
+ if (target.dmPath() != null
&& ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
// If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
// "install-dm").
@@ -503,8 +516,8 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
ArtdDexoptResult result = mInjector.getArtd().dexopt(outputArtifacts,
target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
- target.compilerFilter(), profile, inputVdex, dmFile, priorityClass, dexoptOptions,
- artdCancellationSignal);
+ target.compilerFilter(), profile, inputVdex, target.dmPath(), priorityClass,
+ dexoptOptions, artdCancellationSignal);
// Delete the existing runtime images after the dexopt is performed, even if they are still
// usable (e.g., the compiler filter is "verify"). This is to make sure the dexopt puts the
@@ -545,22 +558,6 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
}
}
- @Nullable
- private DexMetadataPath getDmFile(@NonNull DexInfoType dexInfo) throws RemoteException {
- DexMetadataPath path = buildDmPath(dexInfo);
- if (path == null) {
- return null;
- }
- try {
- if (mInjector.getArtd().getDmFileVisibility(path) != FileVisibility.NOT_FOUND) {
- return path;
- }
- } catch (ServiceSpecificException e) {
- Log.e(TAG, "Failed to check DM file for " + dexInfo.dexPath(), e);
- }
- return null;
- }
-
private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
try {
mInjector.getArtd().commitTmpProfile(profile);
@@ -662,6 +659,7 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
abstract @NonNull String isa();
abstract boolean isInDalvikCache();
abstract @NonNull String compilerFilter();
+ abstract @Nullable DexMetadataPath dmPath();
static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>();
@@ -673,6 +671,7 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
abstract Builder setIsa(@NonNull String value);
abstract Builder setIsInDalvikCache(boolean value);
abstract Builder setCompilerFilter(@NonNull String value);
+ abstract Builder setDmPath(@Nullable DexMetadataPath value);
abstract DexoptTarget<DexInfoType> build();
}
}
@@ -769,5 +768,10 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
public Config getConfig() {
return mConfig;
}
+
+ @NonNull
+ public DexMetadataHelper getDexMetadataHelper() {
+ return new DexMetadataHelper();
+ }
}
}
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 1b9a201b5d..e4032cf570 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -366,13 +366,15 @@ public final class Utils {
* @param refProfile the path where an existing reference profile would be found, if present
* @param externalProfiles a list of external profiles to initialize the reference profile from,
* in the order of preference
+ * @param enableEmbeddedProfile whether to allow initializing the reference profile from the
+ * embedded profile
* @param initOutput the final location to initialize the reference profile to
*/
@NonNull
public static InitProfileResult getOrInitReferenceProfile(@NonNull IArtd artd,
@NonNull String dexPath, @NonNull ProfilePath refProfile,
- @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput)
- throws RemoteException {
+ @NonNull List<ProfilePath> externalProfiles, boolean enableEmbeddedProfile,
+ @NonNull OutputProfile initOutput) throws RemoteException {
try {
if (artd.isProfileUsable(refProfile, dexPath)) {
boolean isOtherReadable =
@@ -387,7 +389,8 @@ public final class Utils {
e);
}
- return initReferenceProfile(artd, dexPath, externalProfiles, initOutput);
+ return initReferenceProfile(
+ artd, dexPath, externalProfiles, enableEmbeddedProfile, initOutput);
}
/**
@@ -400,7 +403,7 @@ public final class Utils {
@Nullable
public static InitProfileResult initReferenceProfile(@NonNull IArtd artd,
@NonNull String dexPath, @NonNull List<ProfilePath> externalProfiles,
- @NonNull OutputProfile output) throws RemoteException {
+ boolean enableEmbeddedProfile, @NonNull OutputProfile output) throws RemoteException {
// Each element is a pair of a profile name (for logging) and the corresponding initializer.
// The order matters. Non-embedded profiles should take precedence.
List<Pair<String, ProfileInitializer>> profileInitializers = new ArrayList<>();
@@ -413,8 +416,10 @@ public final class Utils {
profileInitializers.add(Pair.create(AidlUtils.toString(profile),
() -> artd.copyAndRewriteProfile(profile, output, dexPath)));
}
- profileInitializers.add(Pair.create(
- "embedded profile", () -> artd.copyAndRewriteEmbeddedProfile(output, dexPath)));
+ if (enableEmbeddedProfile) {
+ profileInitializers.add(Pair.create(
+ "embedded profile", () -> artd.copyAndRewriteEmbeddedProfile(output, dexPath)));
+ }
List<String> externalProfileErrors = new ArrayList<>();
for (var pair : profileInitializers) {
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index fc3c4657a0..b7d67eccc3 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -67,6 +67,7 @@ import com.android.server.art.model.DeleteResult;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.proto.DexMetadataConfig;
import com.android.server.art.testing.StaticMockitoRule;
import com.android.server.art.testing.TestingUtils;
import com.android.server.pm.PackageManagerLocal;
@@ -90,6 +91,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
@@ -99,6 +101,7 @@ import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import java.util.zip.ZipFile;
@SmallTest
@RunWith(Parameterized.class)
@@ -128,11 +131,13 @@ public class ArtManagerLocalTest {
@Mock private DexUseManagerLocal mDexUseManager;
@Mock private StorageManager mStorageManager;
@Mock private ArtdRefCache.Pin mArtdPin;
+ @Mock private DexMetadataHelper.Injector mDexMetadataHelperInjector;
private PackageState mPkgState1;
private AndroidPackage mPkg1;
private CheckedSecondaryDexInfo mPkg1SecondaryDexInfo1;
private CheckedSecondaryDexInfo mPkg1SecondaryDexInfoNotFound;
private Config mConfig;
+ private DexMetadataHelper mDexMetadataHelper;
// True if the artifacts should be in dalvik-cache.
@Parameter(0) public boolean mIsInDalvikCache;
@@ -147,6 +152,7 @@ public class ArtManagerLocalTest {
@Before
public void setUp() throws Exception {
mConfig = new Config();
+ mDexMetadataHelper = new DexMetadataHelper(mDexMetadataHelperInjector);
// Use `lenient()` to suppress `UnnecessaryStubbingException` thrown by the strict stubs.
// These are the default test setups. They may or may not be used depending on the code path
@@ -166,6 +172,7 @@ public class ArtManagerLocalTest {
lenient()
.when(mInjector.getArtFileManager())
.thenReturn(new ArtFileManager(mArtFileManagerInjector));
+ lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper);
lenient().when(mArtFileManagerInjector.getArtd()).thenReturn(mArtd);
lenient().when(mArtFileManagerInjector.getUserManager()).thenReturn(mUserManager);
@@ -258,6 +265,10 @@ public class ArtManagerLocalTest {
.when(mArtd.copyAndRewriteEmbeddedProfile(any(), any()))
.thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
+ lenient()
+ .when(mDexMetadataHelperInjector.openZipFile(any()))
+ .thenThrow(NoSuchFileException.class);
+
mArtManagerLocal = new ArtManagerLocal(mInjector);
}
@@ -908,6 +919,44 @@ public class ArtManagerLocalTest {
}
@Test
+ public void testSnapshotAppProfileFromEmbeddedProfile() throws Exception {
+ ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary");
+ String dexPath = "/somewhere/app/foo/base.apk";
+
+ // Simulate that the reference profile doesn't exist.
+ when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false);
+
+ // Verify that the embedded profile is used.
+ when(mArtd.copyAndRewriteEmbeddedProfile(any(), eq(dexPath)))
+ .thenReturn(TestingUtils.createCopyAndRewriteProfileSuccess());
+
+ when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+ }
+
+ @Test
+ public void testSnapshotAppProfileDisableEmbeddedProfile() throws Exception {
+ ProfilePath refProfile = AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary");
+ String dexPath = "/somewhere/app/foo/base.apk";
+
+ // Simulate that the reference profile doesn't exist.
+ when(mArtd.isProfileUsable(deepEq(refProfile), eq(dexPath))).thenReturn(false);
+
+ // The embedded profile is disabled.
+ var config = DexMetadataConfig.newBuilder().setEnableEmbeddedProfile(false).build();
+ String dmPath = TestingUtils.createTempZipWithEntry("config.pb", config.toByteArray());
+ doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(any());
+
+ when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
+
+ mArtManagerLocal.snapshotAppProfile(mSnapshot, PKG_NAME_1, null /* splitName */);
+
+ // Verify that the embedded profile is not used.
+ verify(mArtd, never()).copyAndRewriteEmbeddedProfile(any(), eq(dexPath));
+ }
+
+ @Test
public void testSnapshotAppProfileSplit() throws Exception {
ProfilePath refProfile =
AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split");
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
index b9b8bd31e4..8f7befb957 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -61,6 +61,7 @@ import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentMatcher;
+import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.List;
@@ -271,7 +272,9 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase {
PKG_NAME, APP_VERSION_NAME, APP_VERSION_CODE, ART_VERSION));
when(mArtd.createCancellationSignal()).thenReturn(mock(IArtdCancellationSignal.class));
- when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+ lenient()
+ .when(mDexMetadataHelperInjector.openZipFile(any()))
+ .thenThrow(NoSuchFileException.class);
// The first one is normal.
doReturn(dexoptIsNeeded())
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
index edb90a101c..a58add5f30 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
@@ -47,6 +47,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
+import com.android.server.art.proto.DexMetadataConfig;
import com.android.server.art.testing.TestingUtils;
import org.junit.Before;
@@ -54,6 +55,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
@@ -61,11 +63,13 @@ import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import java.util.zip.ZipFile;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
private final String mDexPath = "/somewhere/app/foo/base.apk";
+ private final String mDmPath = "/somewhere/app/foo/base.dm";
private final ProfilePath mRefProfile =
AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary");
private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
@@ -117,7 +121,9 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
.thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
// By default, no DM file exists.
- lenient().when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+ lenient()
+ .when(mDexMetadataHelperInjector.openZipFile(any()))
+ .thenThrow(NoSuchFileException.class);
// Dexopt is by default needed and successful.
lenient()
@@ -187,9 +193,8 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
@Test
public void testDexoptDm() throws Exception {
- lenient()
- .when(mArtd.getDmFileVisibility(deepEq(mDmFile)))
- .thenReturn(FileVisibility.OTHER_READABLE);
+ String dmPath = TestingUtils.createTempZipWithEntry("primary.vdex", new byte[0] /* data */);
+ doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
verifyStatusAllOk(results);
@@ -415,8 +420,7 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
verifyEmbeddedProfileNotUsed(mDexPath);
}
- @Test
- public void testDexoptUsesEmbeddedProfile() throws Exception {
+ private void checkDexoptUsesEmbeddedProfile() throws Exception {
makeEmbeddedProfileUsable(mDexPath);
List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
@@ -438,6 +442,65 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
}
@Test
+ public void testDexoptUsesEmbeddedProfileNoDm() throws Exception {
+ checkDexoptUsesEmbeddedProfile();
+ }
+
+ @Test
+ public void testDexoptUsesEmbeddedProfileDmNoConfig() throws Exception {
+ String dmPath = TestingUtils.createTempZipWithEntry("primary.vdex", new byte[0] /* data */);
+ doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
+ checkDexoptUsesEmbeddedProfile();
+ }
+
+ @Test
+ public void testDexoptUsesEmbeddedProfileDmEmptyConfig() throws Exception {
+ String dmPath = TestingUtils.createTempZipWithEntry("config.pb", new byte[0] /* data */);
+ doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
+ checkDexoptUsesEmbeddedProfile();
+ }
+
+ @Test
+ public void testDexoptUsesEmbeddedProfileDmBadConfig() throws Exception {
+ String dmPath = TestingUtils.createTempZipWithEntry(
+ "config.pb", new byte[] {0x42, 0x43, 0x44} /* data */);
+ doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
+ checkDexoptUsesEmbeddedProfile();
+ }
+
+ @Test
+ public void testDexoptUsesEmbeddedProfileDmDisableEmbeddedProfile() throws Exception {
+ var config = DexMetadataConfig.newBuilder().setEnableEmbeddedProfile(false).build();
+ String dmPath = TestingUtils.createTempZipWithEntry("config.pb", config.toByteArray());
+ doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
+
+ makeEmbeddedProfileUsable(mDexPath);
+
+ List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+ verifyStatusAllOk(results);
+
+ checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm64", "verify");
+ checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm", "verify");
+
+ verifyEmbeddedProfileNotUsed(mDexPath);
+ }
+
+ @Test
+ public void testDexoptUsesEmbeddedProfileNoEmbeddedProfile() throws Exception {
+ var config = DexMetadataConfig.newBuilder().setEnableEmbeddedProfile(true).build();
+ String dmPath = TestingUtils.createTempZipWithEntry("config.pb", config.toByteArray());
+ doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
+
+ List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+ verifyStatusAllOk(results);
+
+ checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm64", "verify");
+ checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm", "verify");
+
+ verifyEmbeddedProfileNotUsed(mDexPath);
+ }
+
+ @Test
public void testDexoptExternalProfileErrors() throws Exception {
// Having no profile should not be reported.
// Having a bad profile should be reported.
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
index 7a8287fe4c..5e26835da2 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java
@@ -64,12 +64,14 @@ public class PrimaryDexopterTestBase {
@Mock protected UserManager mUserManager;
@Mock protected DexUseManagerLocal mDexUseManager;
@Mock protected StorageManager mStorageManager;
+ @Mock protected DexMetadataHelper.Injector mDexMetadataHelperInjector;
protected PackageState mPkgState;
protected AndroidPackage mPkg;
protected PackageUserState mPkgUserStateNotInstalled;
protected PackageUserState mPkgUserStateInstalled;
protected CancellationSignal mCancellationSignal;
protected Config mConfig;
+ protected DexMetadataHelper mDexMetadataHelper;
@Before
public void setUp() throws Exception {
@@ -79,6 +81,7 @@ public class PrimaryDexopterTestBase {
mPkg = mPkgState.getAndroidPackage();
mCancellationSignal = new CancellationSignal();
mConfig = new Config();
+ mDexMetadataHelper = new DexMetadataHelper(mDexMetadataHelperInjector);
lenient().when(mInjector.getArtd()).thenReturn(mArtd);
lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
@@ -88,6 +91,7 @@ public class PrimaryDexopterTestBase {
lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager);
lenient().when(mInjector.getArtVersion()).thenReturn(ART_VERSION);
lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+ lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper);
lenient()
.when(SystemProperties.get("dalvik.vm.systemuicompilerfilter"))
diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
index f80503d09b..13ff28a908 100644
--- a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java
@@ -103,10 +103,12 @@ public class SecondaryDexopterTest {
@Mock private SecondaryDexopter.Injector mInjector;
@Mock private IArtd mArtd;
@Mock private DexUseManagerLocal mDexUseManager;
+ @Mock private DexMetadataHelper.Injector mDexMetadataHelperInjector;
private PackageState mPkgState;
private AndroidPackage mPkg;
private CancellationSignal mCancellationSignal;
- protected Config mConfig;
+ private Config mConfig;
+ private DexMetadataHelper mDexMetadataHelper;
private SecondaryDexopter mSecondaryDexopter;
@@ -116,6 +118,7 @@ public class SecondaryDexopterTest {
mPkg = mPkgState.getAndroidPackage();
mCancellationSignal = new CancellationSignal();
mConfig = new Config();
+ mDexMetadataHelper = new DexMetadataHelper(mDexMetadataHelperInjector);
lenient()
.when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
@@ -137,6 +140,7 @@ public class SecondaryDexopterTest {
lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false);
lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
lenient().when(mInjector.getConfig()).thenReturn(mConfig);
+ lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper);
List<CheckedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo();
lenient()
diff --git a/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
index ee55170f05..aabc7836b9 100644
--- a/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
+++ b/libartservice/service/javatests/com/android/server/art/testing/TestingUtils.java
@@ -27,9 +27,13 @@ import com.android.server.art.CopyAndRewriteProfileResult;
import com.google.common.truth.Correspondence;
import com.google.common.truth.Truth;
+import java.io.File;
+import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
public final class TestingUtils {
private static final String TAG = "ArtServiceTesting";
@@ -181,6 +185,17 @@ public final class TestingUtils {
return result;
}
+ public static String createTempZipWithEntry(String entryName, byte[] data) throws Exception {
+ File tempFile = File.createTempFile("temp", ".zip");
+ tempFile.deleteOnExit();
+ try (var out = new ZipOutputStream(new FileOutputStream(tempFile))) {
+ out.putNextEntry(new ZipEntry(entryName));
+ out.write(data);
+ out.closeEntry();
+ }
+ return tempFile.getPath();
+ }
+
private static boolean listDeepEquals(
@NonNull List<?> a, @NonNull List<?> b, @NonNull StringBuilder errorMsg) {
if (a.size() != b.size()) {
diff --git a/libartservice/service/proto/dex_metadata_config.proto b/libartservice/service/proto/dex_metadata_config.proto
new file mode 100644
index 0000000000..21cc3b27b8
--- /dev/null
+++ b/libartservice/service/proto/dex_metadata_config.proto
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+syntax = "proto2";
+
+package com.android.server.art.proto;
+option java_multiple_files = true;
+
+// The definition of `config.pb` in a dm file.
+message DexMetadataConfig {
+ // Whether to enable using the embedded profile
+ // (`assets/art-profile/baseline.prof` in the APK/JAR file), if it exists.
+ optional bool enable_embedded_profile = 1 [default = true];
+}