Always pass --dm-fd to dex2oat and change reason to "install-dm".

Bug: 255944781
Test: atest ArtServiceTests
Test: m test-art-host-gtest-art_artd_tests
Ignore-AOSP-First: ART Services.
Change-Id: Ibb8ffd6da39186cb571dbd9e59847bc2161ebd15
diff --git a/artd/artd.cc b/artd/artd.cc
index 32c89c3..2e86b38 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -74,6 +74,7 @@
 namespace {
 
 using ::aidl::com::android::server::art::ArtifactsPath;
+using ::aidl::com::android::server::art::DexMetadataPath;
 using ::aidl::com::android::server::art::DexoptOptions;
 using ::aidl::com::android::server::art::DexoptResult;
 using ::aidl::com::android::server::art::DexoptTrigger;
@@ -550,6 +551,13 @@
   return ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus Artd::getDmFileVisibility(const DexMetadataPath& in_dmFile,
+                                             FileVisibility* _aidl_return) {
+  std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile));
+  *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(dm_path));
+  return ScopedAStatus::ok();
+}
+
 ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profiles,
                                        const std::optional<ProfilePath>& in_referenceProfile,
                                        OutputProfile* in_outputProfile,
@@ -691,6 +699,7 @@
     const std::string& in_compilerFilter,
     const std::optional<ProfilePath>& in_profile,
     const std::optional<VdexPath>& in_inputVdex,
+    const std::optional<DexMetadataPath>& in_dmFile,
     PriorityClass in_priorityClass,
     const DexoptOptions& in_dexoptOptions,
     const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal,
@@ -783,18 +792,20 @@
 
   std::unique_ptr<File> input_vdex_file = nullptr;
   if (in_inputVdex.has_value()) {
-    if (in_inputVdex->getTag() == VdexPath::dexMetadataPath) {
-      std::string input_vdex_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_inputVdex.value()));
-      input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path));
-      args.Add("--dm-fd=%d", input_vdex_file->Fd());
-    } else {
-      std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value()));
-      input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path));
-      args.Add("--input-vdex-fd=%d", input_vdex_file->Fd());
-    }
+    std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value()));
+    input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path));
+    args.Add("--input-vdex-fd=%d", input_vdex_file->Fd());
     fd_logger.Add(*input_vdex_file);
   }
 
+  std::unique_ptr<File> dm_file = nullptr;
+  if (in_dmFile.has_value()) {
+    std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile.value()));
+    dm_file = OR_RETURN_NON_FATAL(OpenFileForReading(dm_path));
+    args.Add("--dm-fd=%d", dm_file->Fd());
+    fd_logger.Add(*dm_file);
+  }
+
   std::unique_ptr<File> profile_file = nullptr;
   if (profile_path.has_value()) {
     profile_file = OR_RETURN_NON_FATAL(OpenFileForReading(profile_path.value()));
diff --git a/artd/artd.h b/artd/artd.h
index 1230ee8..b729c78 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -120,6 +120,10 @@
       const std::string& in_dexFile,
       aidl::com::android::server::art::FileVisibility* _aidl_return) override;
 
+  ndk::ScopedAStatus getDmFileVisibility(
+      const aidl::com::android::server::art::DexMetadataPath& in_dmFile,
+      aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
   ndk::ScopedAStatus getDexoptNeeded(
       const std::string& in_dexFile,
       const std::string& in_instructionSet,
@@ -136,6 +140,7 @@
       const std::string& in_compilerFilter,
       const std::optional<aidl::com::android::server::art::ProfilePath>& in_profile,
       const std::optional<aidl::com::android::server::art::VdexPath>& in_inputVdex,
+      const std::optional<aidl::com::android::server::art::DexMetadataPath>& in_dmFile,
       aidl::com::android::server::art::PriorityClass in_priorityClass,
       const aidl::com::android::server::art::DexoptOptions& in_dexoptOptions,
       const std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>&
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 68d23db..a2ba7f5 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -299,6 +299,8 @@
             PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
         .id = "12345"};
     profile_path_ = tmp_profile_path;
+    vdex_path_ = artifacts_path_;
+    dm_path_ = DexMetadataPath{.dexPath = dex_file_};
     std::filesystem::create_directories(
         std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))).parent_path());
   }
@@ -311,7 +313,7 @@
   void RunDexopt(binder_exception_t expected_status = EX_NONE,
                  Matcher<DexoptResult> aidl_return_matcher = Field(&DexoptResult::cancelled, false),
                  std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
-    InitDexoptInputFiles();
+    InitFilesBeforeDexopt();
     if (cancellation_signal == nullptr) {
       ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
     }
@@ -323,6 +325,7 @@
                                               compiler_filter_,
                                               profile_path_,
                                               vdex_path_,
+                                              dm_path_,
                                               priority_class_,
                                               dexopt_options_,
                                               cancellation_signal,
@@ -360,23 +363,32 @@
   std::optional<std::string> class_loader_context_;
   std::string compiler_filter_;
   std::optional<VdexPath> vdex_path_;
+  std::optional<DexMetadataPath> dm_path_;
   PriorityClass priority_class_ = PriorityClass::BACKGROUND;
   DexoptOptions dexopt_options_;
   std::optional<ProfilePath> profile_path_;
 
  private:
-  void InitDexoptInputFiles() {
+  void InitFilesBeforeDexopt() {
+    // Required files.
     CreateFile(dex_file_);
+
+    // Optional files.
     if (vdex_path_.has_value()) {
-      if (vdex_path_->getTag() == VdexPath::dexMetadataPath) {
-        CreateFile(OR_FATAL(BuildDexMetadataPath(vdex_path_.value())));
-      } else {
-        CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())));
-      }
+      CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())), "old_vdex");
+    }
+    if (dm_path_.has_value()) {
+      CreateFile(OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
     }
     if (profile_path_.has_value()) {
       CreateFile(OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
     }
+
+    // Files to be replaced.
+    std::string oat_path = OR_FATAL(BuildOatPath(artifacts_path_));
+    CreateFile(oat_path, "old_oat");
+    CreateFile(OatPathToVdexPath(oat_path), "old_vdex");
+    CreateFile(OatPathToArtPath(oat_path), "old_art");
   }
 };
 
@@ -485,16 +497,18 @@
           WhenSplitBy(
               "--",
               AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
-              AllOf(Contains(art_root_ + "/bin/dex2oat32"),
-                    Contains(Flag("--zip-fd=", FdOf(dex_file_))),
-                    Contains(Flag("--zip-location=", dex_file_)),
-                    Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")),
-                    Contains(Flag("--instruction-set=", "arm64")),
-                    Contains(Flag("--compiler-filter=", "speed")),
-                    Contains(
-                        Flag("--profile-file-fd=",
-                             FdOf(android_data_ +
-                                  "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"))))),
+              AllOf(
+                  Contains(art_root_ + "/bin/dex2oat32"),
+                  Contains(Flag("--zip-fd=", FdOf(dex_file_))),
+                  Contains(Flag("--zip-location=", dex_file_)),
+                  Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")),
+                  Contains(Flag("--instruction-set=", "arm64")),
+                  Contains(Flag("--compiler-filter=", "speed")),
+                  Contains(Flag("--profile-file-fd=",
+                                FdOf(android_data_ +
+                                     "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"))),
+                  Contains(Flag("--input-vdex-fd=", FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))),
+                  Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))))),
           _,
           _))
       .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
@@ -542,47 +556,23 @@
   RunDexopt();
 }
 
-TEST_F(ArtdTest, dexoptNoInputVdex) {
+TEST_F(ArtdTest, dexoptNoOptionalInputFiles) {
+  profile_path_ = std::nullopt;
+  vdex_path_ = std::nullopt;
+  dm_path_ = std::nullopt;
+
   EXPECT_CALL(*mock_exec_utils_,
               DoExecAndReturnCode(WhenSplitBy("--",
                                               _,
-                                              AllOf(Not(Contains(Flag("--dm-fd=", _))),
-                                                    Not(Contains(Flag("--input-vdex-fd=", _))))),
+                                              AllOf(Not(Contains(Flag("--profile-file-fd=", _))),
+                                                    Not(Contains(Flag("--input-vdex-fd=", _))),
+                                                    Not(Contains(Flag("--dm-fd=", _))))),
                                   _,
                                   _))
       .WillOnce(Return(0));
   RunDexopt();
 }
 
-TEST_F(ArtdTest, dexoptInputVdex) {
-  vdex_path_ = artifacts_path_;
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(
-                  WhenSplitBy("--",
-                              _,
-                              AllOf(Not(Contains(Flag("--dm-fd=", _))),
-                                    Contains(Flag("--input-vdex-fd=",
-                                                  FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))))),
-                  _,
-                  _))
-      .WillOnce(Return(0));
-  RunDexopt();
-}
-
-TEST_F(ArtdTest, dexoptInputVdexDm) {
-  vdex_path_ = DexMetadataPath{.dexPath = dex_file_};
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(
-                  WhenSplitBy("--",
-                              _,
-                              AllOf(Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))),
-                                    Not(Contains(Flag("--input-vdex-fd=", _))))),
-                  _,
-                  _))
-      .WillOnce(Return(0));
-  RunDexopt();
-}
-
 TEST_F(ArtdTest, dexoptPriorityClassBoot) {
   priority_class_ = PriorityClass::BOOT;
   EXPECT_CALL(*mock_exec_utils_,
@@ -873,15 +863,15 @@
 TEST_F(ArtdTest, dexoptFailed) {
   dexopt_options_.generateAppImage = true;
   EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
-      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
-                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
-                      WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")),
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      WithArg<0>(WriteToFdFlag("--app-image-fd=", "new_art")),
                       Return(1)));
   RunDexopt(EX_SERVICE_SPECIFIC);
 
-  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.odex"));
-  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.vdex"));
-  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art"));
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
 }
 
 TEST_F(ArtdTest, dexoptCancelledBeforeDex2oat) {
@@ -902,8 +892,9 @@
 
   RunDexopt(EX_NONE, Field(&DexoptResult::cancelled, true), cancellation_signal);
 
-  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.odex"));
-  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.vdex"));
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
 }
 
 TEST_F(ArtdTest, dexoptCancelledDuringDex2oat) {
@@ -948,8 +939,9 @@
   t.join();
 
   // Step 6.
-  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.odex"));
-  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.vdex"));
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
 }
 
 TEST_F(ArtdTest, dexoptCancelledAfterDex2oat) {
@@ -959,11 +951,13 @@
   constexpr pid_t kPid = 123;
 
   EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
-      .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
-        callbacks.on_start(kPid);
-        callbacks.on_end(kPid);
-        return 0;
-      });
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      [&](auto, const ExecCallbacks& callbacks, auto) {
+                        callbacks.on_start(kPid);
+                        callbacks.on_end(kPid);
+                        return 0;
+                      }));
   EXPECT_CALL(mock_kill_, Call).Times(0);
 
   RunDexopt(EX_NONE, Field(&DexoptResult::cancelled, false), cancellation_signal);
@@ -971,8 +965,9 @@
   // This signal should be ignored.
   cancellation_signal->cancel();
 
-  EXPECT_TRUE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.odex"));
-  EXPECT_TRUE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.vdex"));
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "new_oat");
+  CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "new_vdex");
+  EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art"));
 }
 
 TEST_F(ArtdTest, isProfileUsable) {
@@ -1158,128 +1153,138 @@
   EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
 }
 
-TEST_F(ArtdTest, getProfileVisibilityOtherReadable) {
-  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
-  CreateFile(profile_file);
-  std::filesystem::permissions(
-      profile_file, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+class ArtdGetVisibilityTest : public ArtdTest {
+ protected:
+  template <typename PathType>
+  using Method = ndk::ScopedAStatus (Artd::*)(const PathType&, FileVisibility*);
 
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getProfileVisibility(profile_path_.value(), &result).isOk());
-  EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+  template <typename PathType>
+  void TestGetVisibilityOtherReadable(Method<PathType> method,
+                                      const PathType& input,
+                                      const std::string& path) {
+    CreateFile(path);
+    std::filesystem::permissions(
+        path, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityNotOtherReadable(Method<PathType> method,
+                                         const PathType& input,
+                                         const std::string& path) {
+    CreateFile(path);
+    std::filesystem::permissions(
+        path, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
+
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityNotFound(Method<PathType> method, const PathType& input) {
+    FileVisibility result;
+    ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
+    EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+  }
+
+  template <typename PathType>
+  void TestGetVisibilityPermissionDenied(Method<PathType> method,
+                                         const PathType& input,
+                                         const std::string& path) {
+    CreateFile(path);
+
+    auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(path).parent_path());
+    auto scoped_unroot = ScopedUnroot();
+
+    FileVisibility result;
+    ndk::ScopedAStatus status = ((*artd_).*method)(input, &result);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+    EXPECT_THAT(status.getMessage(), HasSubstr("Failed to get status of"));
+  }
+};
+
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getProfileVisibility,
+                                 profile_path_.value(),
+                                 OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
 }
 
-TEST_F(ArtdTest, getProfileVisibilityNotOtherReadable) {
-  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
-  CreateFile(profile_file);
-  std::filesystem::permissions(
-      profile_file, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
-
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getProfileVisibility(profile_path_.value(), &result).isOk());
-  EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getProfileVisibility,
+                                    profile_path_.value(),
+                                    OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
 }
 
-TEST_F(ArtdTest, getProfileVisibilityNotFound) {
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getProfileVisibility(profile_path_.value(), &result).isOk());
-  EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getProfileVisibility, profile_path_.value());
 }
 
-TEST_F(ArtdTest, getProfileVisibilityPermissionDenied) {
-  std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
-  CreateFile(profile_file);
-
-  auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(profile_file).parent_path());
-  auto scoped_unroot = ScopedUnroot();
-
-  FileVisibility result;
-  ndk::ScopedAStatus status = artd_->getProfileVisibility(profile_path_.value(), &result);
-  EXPECT_FALSE(status.isOk());
-  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
-  EXPECT_THAT(status.getMessage(),
-              ContainsRegex(R"re(Failed to get status of .*primary\.prof\.12345\.tmp)re"));
+TEST_F(ArtdGetVisibilityTest, getProfileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getProfileVisibility,
+                                    profile_path_.value(),
+                                    OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
 }
 
-TEST_F(ArtdTest, getArtifactsVisibilityOtherReadable) {
-  std::string oat_file = OR_FATAL(BuildOatPath(artifacts_path_));
-  CreateFile(oat_file);
-  std::filesystem::permissions(
-      oat_file, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
-
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getArtifactsVisibility(artifacts_path_, &result).isOk());
-  EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
 }
 
-TEST_F(ArtdTest, getArtifactsVisibilityNotOtherReadable) {
-  std::string oat_file = OR_FATAL(BuildOatPath(artifacts_path_));
-  CreateFile(oat_file);
-  std::filesystem::permissions(
-      oat_file, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
-
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getArtifactsVisibility(artifacts_path_, &result).isOk());
-  EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
 }
 
-TEST_F(ArtdTest, getArtifactsVisibilityNotFound) {
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getArtifactsVisibility(artifacts_path_, &result).isOk());
-  EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getArtifactsVisibility, artifacts_path_);
 }
 
-TEST_F(ArtdTest, getArtifactsVisibilityPermissionDenied) {
-  std::string oat_file = OR_FATAL(BuildOatPath(artifacts_path_));
-  CreateFile(oat_file);
-
-  auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(oat_file).parent_path());
-  auto scoped_unroot = ScopedUnroot();
-
-  FileVisibility result;
-  ndk::ScopedAStatus status = artd_->getArtifactsVisibility(artifacts_path_, &result);
-  EXPECT_FALSE(status.isOk());
-  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
-  EXPECT_THAT(status.getMessage(), ContainsRegex(R"re(Failed to get status of .*b\.odex)re"));
+TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(
+      &Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
 }
 
-TEST_F(ArtdTest, getDexFileVisibilityOtherReadable) {
-  CreateFile(dex_file_);
-  std::filesystem::permissions(
-      dex_file_, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
-
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getDexFileVisibility(dex_file_, &result).isOk());
-  EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
 }
 
-TEST_F(ArtdTest, getDexFileVisibilityNotOtherReadable) {
-  CreateFile(dex_file_);
-  std::filesystem::permissions(
-      dex_file_, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
-
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getDexFileVisibility(dex_file_, &result).isOk());
-  EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
 }
 
-TEST_F(ArtdTest, getDexFileVisibilityNotFound) {
-  FileVisibility result;
-  ASSERT_TRUE(artd_->getDexFileVisibility(dex_file_, &result).isOk());
-  EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getDexFileVisibility, dex_file_);
 }
 
-TEST_F(ArtdTest, getDexFileVisibilityPermissionDenied) {
-  CreateFile(dex_file_);
+TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getDexFileVisibility, dex_file_, dex_file_);
+}
 
-  auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(dex_file_).parent_path());
-  auto scoped_unroot = ScopedUnroot();
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityOtherReadable) {
+  TestGetVisibilityOtherReadable(&Artd::getDmFileVisibility,
+                                 dm_path_.value(),
+                                 OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
 
-  FileVisibility result;
-  ndk::ScopedAStatus status = artd_->getDexFileVisibility(dex_file_, &result);
-  EXPECT_FALSE(status.isOk());
-  EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
-  EXPECT_THAT(status.getMessage(), ContainsRegex(R"re(Failed to get status of .*/a/b\.apk)re"));
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotOtherReadable) {
+  TestGetVisibilityNotOtherReadable(&Artd::getDmFileVisibility,
+                                    dm_path_.value(),
+                                    OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotFound) {
+  TestGetVisibilityNotFound(&Artd::getDmFileVisibility, dm_path_.value());
+}
+
+TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityPermissionDenied) {
+  TestGetVisibilityPermissionDenied(&Artd::getDmFileVisibility,
+                                    dm_path_.value(),
+                                    OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
 }
 
 TEST_F(ArtdTest, mergeProfiles) {
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index d575adf..19c1d66 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -110,6 +110,14 @@
     com.android.server.art.FileVisibility getDexFileVisibility(@utf8InCpp String dexFile);
 
     /**
+     * Returns the visibility of the DM file.
+     *
+     * Throws fatal and non-fatal errors.
+     */
+    com.android.server.art.FileVisibility getDmFileVisibility(
+            in com.android.server.art.DexMetadataPath dmFile);
+
+    /**
      * Returns true if dexopt is needed. `dexoptTrigger` is a bit field that consists of values
      * defined in `com.android.server.art.DexoptTrigger`.
      *
@@ -131,6 +139,7 @@
             @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
             in @nullable com.android.server.art.ProfilePath profile,
             in @nullable com.android.server.art.VdexPath inputVdex,
+            in @nullable com.android.server.art.DexMetadataPath dmFile,
             com.android.server.art.PriorityClass priorityClass,
             in com.android.server.art.DexoptOptions dexoptOptions,
             in com.android.server.art.IArtdCancellationSignal cancellationSignal);
diff --git a/artd/binder/com/android/server/art/VdexPath.aidl b/artd/binder/com/android/server/art/VdexPath.aidl
index ec23a0e..6112e7a 100644
--- a/artd/binder/com/android/server/art/VdexPath.aidl
+++ b/artd/binder/com/android/server/art/VdexPath.aidl
@@ -24,6 +24,4 @@
 union VdexPath {
     /** Represents a VDEX file as part of the artifacts. */
     com.android.server.art.ArtifactsPath artifactsPath;
-    /** Represents a VDEX file in a dex metadata file. */
-    com.android.server.art.DexMetadataPath dexMetadataPath;
 }
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
index 8dcc1e9..295b023 100644
--- a/artd/path_utils.cc
+++ b/artd/path_utils.cc
@@ -210,11 +210,6 @@
   return ReplaceFileExtension(dex_metadata_path.dexPath, "dm");
 }
 
-Result<std::string> BuildDexMetadataPath(const VdexPath& vdex_path) {
-  DCHECK(vdex_path.getTag() == VdexPath::dexMetadataPath);
-  return BuildDexMetadataPath(vdex_path.get<VdexPath::dexMetadataPath>());
-}
-
 Result<std::string> BuildProfileOrDmPath(const ProfilePath& profile_path) {
   switch (profile_path.getTag()) {
     case ProfilePath::primaryRefProfilePath:
diff --git a/artd/path_utils.h b/artd/path_utils.h
index bfbbd80..0cc017e 100644
--- a/artd/path_utils.h
+++ b/artd/path_utils.h
@@ -70,9 +70,6 @@
 android::base::Result<std::string> BuildDexMetadataPath(
     const aidl::com::android::server::art::DexMetadataPath& dex_metadata_path);
 
-android::base::Result<std::string> BuildDexMetadataPath(
-    const aidl::com::android::server::art::VdexPath& vdex_path);
-
 android::base::Result<std::string> BuildProfileOrDmPath(
     const aidl::com::android::server::art::ProfilePath& profile_path);
 
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
index 508d8c2..b0e1a25 100644
--- a/artd/path_utils_test.cc
+++ b/artd/path_utils_test.cc
@@ -241,11 +241,6 @@
   EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
 }
 
-TEST_F(PathUtilsTest, BuildDexMetadataPathForVdex) {
-  EXPECT_THAT(BuildDexMetadataPath(VdexPath(DexMetadataPath{.dexPath = "/a/b.apk"})),
-              HasValue("/a/b.dm"));
-}
-
 TEST_F(PathUtilsTest, BuildProfilePath) {
   EXPECT_THAT(BuildProfileOrDmPath(PrimaryRefProfilePath{.packageName = "com.android.foo",
                                                          .profileName = "primary"}),
diff --git a/libartservice/service/java/com/android/server/art/DexOptimizer.java b/libartservice/service/java/com/android/server/art/DexOptimizer.java
index 8cfe8ad..356d74b 100644
--- a/libartservice/service/java/com/android/server/art/DexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/DexOptimizer.java
@@ -148,13 +148,13 @@
                     long wallTimeMs = 0;
                     long cpuTimeMs = 0;
                     try {
-                        DexoptTarget target = DexoptTarget.builder()
+                        var target = DexoptTarget.<DexInfoType>builder()
                                                       .setDexInfo(dexInfo)
                                                       .setIsa(abi.isa())
                                                       .setIsInDalvikCache(isInDalvikCache())
                                                       .setCompilerFilter(compilerFilter)
                                                       .build();
-                        GetDexoptNeededOptions options =
+                        var options =
                                 GetDexoptNeededOptions.builder()
                                         .setProfileMerged(profileMerged)
                                         .setFlags(mParams.getFlags())
@@ -346,7 +346,7 @@
     }
 
     @NonNull
-    GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget target,
+    GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target,
             @NonNull GetDexoptNeededOptions options) throws RemoteException {
         int dexoptTrigger = getDexoptTrigger(target, options);
 
@@ -362,8 +362,8 @@
         return result;
     }
 
-    int getDexoptTrigger(@NonNull DexoptTarget target, @NonNull GetDexoptNeededOptions options)
-            throws RemoteException {
+    int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target,
+            @NonNull GetDexoptNeededOptions options) throws RemoteException {
         if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
             return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME
                     | DexoptTrigger.COMPILER_FILTER_IS_WORSE
@@ -396,8 +396,8 @@
         return dexoptTrigger;
     }
 
-    private DexoptResult dexoptFile(@NonNull DexoptTarget target, @Nullable ProfilePath profile,
-            @NonNull GetDexoptNeededResult getDexoptNeededResult,
+    private DexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target,
+            @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
             @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
             @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)
             throws RemoteException {
@@ -407,9 +407,25 @@
         VdexPath inputVdex =
                 getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
 
+        DexMetadataPath dmFile = getDmFile(target.dexInfo());
+        if (dmFile != null
+                && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
+            // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
+            // "install-dm").
+            // Note that this only applies to reasons for app install because the goal is to give
+            // Play a signal that a DM file is downloaded at install time. We actually pass the DM
+            // file regardless of the compilation reason, but we don't append a suffix when the
+            // compilation reason is not a reason for app install.
+            // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by
+            // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The
+            // compilation reason can still be "install-dm" even if dex2oat left all contents of the
+            // DM file unused or an empty DM file is passed to dex2oat.
+            dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
+        }
+
         return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
                 target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
-                priorityClass, dexoptOptions, artdCancellationSignal);
+                dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
     }
 
     @Nullable
@@ -426,7 +442,8 @@
                 return VdexPath.artifactsPath(
                         AidlUtils.buildArtifactsPath(dexPath, isa, false /* isInDalvikCache */));
             case ArtifactsLocation.DM:
-                return VdexPath.dexMetadataPath(AidlUtils.buildDexMetadataPath(dexPath));
+                // The DM file is passed to dex2oat as a separate flag whenever it exists.
+                return null;
             default:
                 // This should never happen as the value is got from artd.
                 throw new IllegalStateException(
@@ -434,6 +451,22 @@
         }
     }
 
+    @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);
@@ -525,24 +558,30 @@
     /** Returns the paths to the current profiles of the given dex file. */
     @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo);
 
+    /**
+     * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file
+     * should be passed.
+     */
+    @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo);
+
     @AutoValue
-    abstract static class DexoptTarget {
-        abstract @NonNull DetailedDexInfo dexInfo();
+    abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> {
+        abstract @NonNull DexInfoType dexInfo();
         abstract @NonNull String isa();
         abstract boolean isInDalvikCache();
         abstract @NonNull String compilerFilter();
 
-        static Builder builder() {
-            return new AutoValue_DexOptimizer_DexoptTarget.Builder();
+        static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
+            return new AutoValue_DexOptimizer_DexoptTarget.Builder<DexInfoType>();
         }
 
         @AutoValue.Builder
-        abstract static class Builder {
-            abstract Builder setDexInfo(@NonNull DetailedDexInfo value);
+        abstract static class Builder<DexInfoType extends DetailedDexInfo> {
+            abstract Builder setDexInfo(@NonNull DexInfoType value);
             abstract Builder setIsa(@NonNull String value);
             abstract Builder setIsInDalvikCache(boolean value);
             abstract Builder setCompilerFilter(@NonNull String value);
-            abstract DexoptTarget build();
+            abstract DexoptTarget<DexInfoType> build();
         }
     }
 
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
index 032f2ae..ee36b08 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
@@ -206,6 +206,12 @@
         return profiles;
     }
 
+    @Override
+    @Nullable
+    protected DexMetadataPath buildDmPath(@NonNull DetailedPrimaryDexInfo dexInfo) {
+        return AidlUtils.buildDexMetadataPath(dexInfo.dexPath());
+    }
+
     private boolean isSharedLibrary() {
         // TODO(b/242688548): Package manager should provide a better API for this.
         return !TextUtils.isEmpty(mPkg.getSdkLibraryName())
diff --git a/libartservice/service/java/com/android/server/art/ReasonMapping.java b/libartservice/service/java/com/android/server/art/ReasonMapping.java
index 88a330c..08140ad 100644
--- a/libartservice/service/java/com/android/server/art/ReasonMapping.java
+++ b/libartservice/service/java/com/android/server/art/ReasonMapping.java
@@ -27,6 +27,8 @@
 
 import dalvik.system.DexFile;
 
+import java.util.Set;
+
 /**
  * Maps a compilation reason to a compiler filter and a priority class.
  *
@@ -57,6 +59,11 @@
     public static final String REASON_INSTALL_BULK_SECONDARY_DOWNGRADED =
             "install-bulk-secondary-downgraded";
 
+    /** @hide */
+    public static final Set<String> REASONS_FOR_INSTALL = Set.of(REASON_INSTALL,
+            REASON_INSTALL_FAST, REASON_INSTALL_BULK, REASON_INSTALL_BULK_SECONDARY,
+            REASON_INSTALL_BULK_DOWNGRADED, REASON_INSTALL_BULK_SECONDARY_DOWNGRADED);
+
     /**
      * Loads the compiler filter from the system property for the given reason and checks for
      * validity.
diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java
index 95b9ddb..73987fe 100644
--- a/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java
@@ -135,6 +135,12 @@
         return List.of(AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
     }
 
+    @Override
+    @Nullable
+    protected DexMetadataPath buildDmPath(@NonNull DetailedSecondaryDexInfo dexInfo) {
+        return null;
+    }
+
     private int getUid(@NonNull DetailedSecondaryDexInfo dexInfo) {
         return dexInfo.userHandle().getUid(mPkgState.getAppId());
     }
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
index 2fdf1ce..4abf4c3 100644
--- a/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
+++ b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
@@ -109,6 +109,17 @@
          *   <li>{@code "unknown"}: if the reason is empty or the optimized artifacts do not exist.
          *   <li>{@code "error"}: if an unexpected error occurs.
          * </ul>
+         *
+         * Note that this value can differ from the requested compilation reason passed to {@link
+         * OptimizeParams.Builder}. Specifically, if the requested reason is for app install (e.g.,
+         * "install"), and a DM file is passed to {@code dex2oat}, a "-dm" suffix will be appended
+         * to the actual reason (e.g., "install-dm"). Other compilation reasons remain unchanged
+         * even if a DM file is passed to {@code dex2oat}.
+         *
+         * Also note that the "-dm" suffix does <b>not</b> imply anything in the DM file being used
+         * by {@code dex2oat}. The compilation reason can still be "install-dm" even if {@code
+         * dex2oat} left all contents of the DM file unused or an empty DM file is passed to
+         * {@code dex2oat}.
          */
         public abstract @NonNull String getCompilationReason();
 
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
index 407fd61..bff34ee 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
@@ -209,6 +209,7 @@
         dexoptOptions.hiddenApiPolicyEnabled = mParams.mExpectedIsHiddenApiPolicyEnabled;
 
         when(mArtd.createCancellationSignal()).thenReturn(mock(IArtdCancellationSignal.class));
+        when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
 
         // The first one is normal.
         doReturn(dexoptIsNeeded())
@@ -222,8 +223,8 @@
                                 mParams.mExpectedIsInDalvikCache, permissionSettings)),
                         eq("/data/app/foo/base.apk"), eq("arm64"), eq("PCL[]"),
                         eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
-                        isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE),
-                        deepEq(dexoptOptions), any());
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
 
         // The second one fails on `dexopt`.
         doReturn(dexoptIsNeeded())
@@ -236,8 +237,8 @@
                                 mParams.mExpectedIsInDalvikCache, permissionSettings)),
                         eq("/data/app/foo/base.apk"), eq("arm"), eq("PCL[]"),
                         eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
-                        isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE),
-                        deepEq(dexoptOptions), any());
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
 
         // The third one doesn't need dexopt.
         doReturn(dexoptIsNotNeeded())
@@ -257,8 +258,8 @@
                                 mParams.mExpectedIsInDalvikCache, permissionSettings)),
                         eq("/data/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"),
                         eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
-                        isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE),
-                        deepEq(dexoptOptions), any());
+                        isNull() /* inputVdex */, isNull() /* dmFile */,
+                        eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions), any());
 
         assertThat(mPrimaryDexOptimizer.dexopt())
                 .comparingElementsUsing(TestingUtils.<DexContainerFileOptimizeResult>deepEquality())
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
index 5b9264d..ff74e15 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -73,6 +73,7 @@
             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary");
     private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
     private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
+    private final DexMetadataPath mDmFile = AidlUtils.buildDexMetadataPath(mDexPath);
     private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary(
             PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */);
     private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary(
@@ -106,13 +107,16 @@
         lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
         lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false);
 
+        // By default, no DM file exists.
+        lenient().when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
         // 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(), any()))
+                .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any()))
                 .thenReturn(mDexoptResult);
 
         lenient()
@@ -133,8 +137,8 @@
                 .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt());
         doReturn(mDexoptResult)
                 .when(mArtd)
-                .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), anyInt(),
-                        any(), any());
+                .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), any(),
+                        anyInt(), any(), any());
 
         // ArtifactsPath, isInDalvikCache=true.
         doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE))
@@ -145,7 +149,7 @@
                 .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
                         deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
                                 mDexPath, "arm", true /* isInDalvikCache */))),
-                        anyInt(), any(), any());
+                        any(), anyInt(), any(), any());
 
         // ArtifactsPath, isInDalvikCache=false.
         doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX))
@@ -156,7 +160,7 @@
                 .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(),
                         deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
                                 mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
-                        anyInt(), any(), any());
+                        any(), anyInt(), any(), any());
 
         // DexMetadataPath.
         doReturn(dexoptIsNeeded(ArtifactsLocation.DM))
@@ -164,15 +168,34 @@
                 .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt());
         doReturn(mDexoptResult)
                 .when(mArtd)
-                .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(),
-                        deepEq(VdexPath.dexMetadataPath(
-                                AidlUtils.buildDexMetadataPath(mSplit0DexPath))),
+                .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(),
                         anyInt(), any(), any());
 
         mPrimaryDexOptimizer.dexopt();
     }
 
     @Test
+    public void testDexoptDm() throws Exception {
+        lenient()
+                .when(mArtd.getDmFileVisibility(deepEq(mDmFile)))
+                .thenReturn(FileVisibility.OTHER_READABLE);
+
+        mPrimaryDexOptimizer.dexopt();
+
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), deepEq(mDmFile),
+                        anyInt(),
+                        argThat(dexoptOptions
+                                -> dexoptOptions.compilationReason.equals("install-dm")),
+                        any());
+        verify(mArtd, times(2))
+                .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), isNull(),
+                        anyInt(),
+                        argThat(dexoptOptions -> dexoptOptions.compilationReason.equals("install")),
+                        any());
+    }
+
+    @Test
     public void testDexoptUsesRefProfile() throws Exception {
         makeProfileUsable(mRefProfile);
         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
@@ -342,8 +365,8 @@
         makeProfileNotUsable(mPrebuiltProfile);
         makeProfileUsable(mDmProfile);
 
-        when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), anyInt(), any(),
-                     any()))
+        when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
+                     any(), any()))
                 .thenThrow(ServiceSpecificException.class);
 
         mPrimaryDexOptimizer.dexopt();
@@ -448,7 +471,7 @@
                     true /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */);
         })
                 .when(mArtd)
-                .dexopt(any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
                         same(artdCancellationSignal));
 
         // The result should only contain one element: the result of the first file with
@@ -462,7 +485,8 @@
         // It shouldn't continue after being cancelled on the first file.
         verify(mArtd, times(1)).createCancellationSignal();
         verify(mArtd, times(1))
-                .dexopt(any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), any());
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
     }
 
     @Test
@@ -481,7 +505,7 @@
                     true /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */);
         })
                 .when(mArtd)
-                .dexopt(any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
                         same(artdCancellationSignal));
         doAnswer(invocation -> {
             dexoptCancelled.release();
@@ -507,7 +531,8 @@
         // It shouldn't continue after being cancelled on the first file.
         verify(mArtd, times(1)).createCancellationSignal();
         verify(mArtd, times(1))
-                .dexopt(any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), any());
+                .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
+                        any());
     }
 
     private void checkDexoptWithPublicProfile(
@@ -515,8 +540,8 @@
         artd.dexopt(
                 argThat(artifacts
                         -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
-                eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), anyInt(),
-                argThat(dexoptOptions -> dexoptOptions.generateAppImage == true), any());
+                eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), any(),
+                anyInt(), argThat(dexoptOptions -> dexoptOptions.generateAppImage == true), any());
     }
 
     private void checkDexoptWithPrivateProfile(
@@ -524,8 +549,8 @@
         artd.dexopt(
                 argThat(artifacts
                         -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == false),
-                eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), anyInt(),
-                argThat(dexoptOptions -> dexoptOptions.generateAppImage == true), any());
+                eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), any(),
+                anyInt(), argThat(dexoptOptions -> dexoptOptions.generateAppImage == true), any());
     }
 
     private void checkDexoptWithNoProfile(
@@ -533,7 +558,7 @@
         artd.dexopt(
                 argThat(artifacts
                         -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
-                eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), anyInt(),
+                eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), any(), anyInt(),
                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
     }
 
diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
index 7b967e4..4e9a7f1 100644
--- a/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
@@ -145,8 +145,8 @@
                 .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
                 .thenReturn(dexoptIsNeeded());
         lenient()
-                .when(mArtd.dexopt(
-                        any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), any()))
+                .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
+                        any(), any()))
                 .thenReturn(createDexoptResult());
 
         lenient()
@@ -316,7 +316,7 @@
         OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
                 dexPath, isa, false /* isInDalvikCache */, permissionSettings);
         artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
-                eq("speed-profile"), deepEq(profile), any(), anyInt(),
+                eq("speed-profile"), deepEq(profile), any(), isNull() /* dmFile */, anyInt(),
                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
     }
 
@@ -326,7 +326,7 @@
         OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
                 dexPath, isa, false /* isInDalvikCache */, permissionSettings);
         artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
-                eq(compilerFilter), isNull(), any(), anyInt(),
+                eq(compilerFilter), isNull(), any(), isNull() /* dmFile */, anyInt(),
                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
     }