Update artd to support profiles for secondary dex files.

Bug: 249984283
Test: atest ArtServiceTests
Test: m test-art-host-gtest-art_artd_tests
Ignore-AOSP-First: ART Services.
Change-Id: I35e19e7fa2147717754feddbc72a46983dc0d9d8
diff --git a/artd/artd.cc b/artd/artd.cc
index bf2f7b3..ce42a86 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -102,7 +102,7 @@
 using ::fmt::literals::operator""_format;  // NOLINT
 
 using ArtifactsLocation = GetDexoptNeededResult::ArtifactsLocation;
-using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
 
 constexpr const char* kServiceName = "artd";
 constexpr const char* kArtdCancellationSignalType = "ArtdCancellationSignal";
@@ -448,7 +448,7 @@
                                                const std::string& in_dexFile,
                                                bool* _aidl_return) {
   std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src));
-  std::string dst_path = OR_RETURN_FATAL(BuildRefProfilePath(in_dst->profilePath.refProfilePath));
+  std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_dst->profilePath));
   OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
 
   CmdlineBuilder args;
@@ -504,9 +504,9 @@
   return ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Artd::commitTmpProfile(const TmpRefProfilePath& in_profile) {
-  std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpRefProfilePath(in_profile));
-  std::string ref_profile_path = OR_RETURN_FATAL(BuildRefProfilePath(in_profile.refProfilePath));
+ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) {
+  std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile));
+  std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile));
 
   std::error_code ec;
   std::filesystem::rename(tmp_profile_path, ref_profile_path, ec);
@@ -557,7 +557,7 @@
     profile_paths.push_back(std::move(profile_path));
   }
   std::string output_profile_path =
-      OR_RETURN_FATAL(BuildRefProfilePath(in_outputProfile->profilePath.refProfilePath));
+      OR_RETURN_FATAL(BuildFinalProfilePath(in_outputProfile->profilePath));
   OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
 
   CmdlineBuilder args;
diff --git a/artd/artd.h b/artd/artd.h
index 9c65ed2..c775c4a 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -95,7 +95,7 @@
       bool* _aidl_return) override;
 
   ndk::ScopedAStatus commitTmpProfile(
-      const aidl::com::android::server::art::ProfilePath::TmpRefProfilePath& in_profile) override;
+      const aidl::com::android::server::art::ProfilePath::TmpProfilePath& in_profile) override;
 
   ndk::ScopedAStatus deleteProfile(
       const aidl::com::android::server::art::ProfilePath& in_profile) override;
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 6b54517..76cba88 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -97,9 +97,9 @@
 using ::testing::SetArgPointee;
 using ::testing::WithArg;
 
-using CurProfilePath = ProfilePath::CurProfilePath;
-using RefProfilePath = ProfilePath::RefProfilePath;
-using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
 
 using ::fmt::literals::operator""_format;  // NOLINT
 
@@ -294,14 +294,13 @@
     clc_2_ = GetTestDexFileName("Nested");
     class_loader_context_ = "PCL[{}:{}]"_format(clc_1_, clc_2_);
     compiler_filter_ = "speed";
-    TmpRefProfilePath tmp_ref_profile_path{
-        .refProfilePath =
-            RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+    TmpProfilePath tmp_profile_path{
+        .finalPath =
+            PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
         .id = "12345"};
-    profile_path_ = tmp_ref_profile_path;
+    profile_path_ = tmp_profile_path;
     std::filesystem::create_directories(
-        std::filesystem::path(OR_FATAL(BuildTmpRefProfilePath(tmp_ref_profile_path)))
-            .parent_path());
+        std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))).parent_path());
   }
 
   void TearDown() override {
@@ -1019,8 +1018,8 @@
 }
 
 TEST_F(ArtdTest, copyAndRewriteProfile) {
-  const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
-  std::string src_file = OR_FATAL(BuildTmpRefProfilePath(src));
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
   CreateFile(src_file, "abc");
   OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
   dst.profilePath.id = "";
@@ -1045,12 +1044,12 @@
   EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
   EXPECT_TRUE(result);
   EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
-  CheckContent(OR_FATAL(BuildTmpRefProfilePath(dst.profilePath)), "def");
+  CheckContent(OR_FATAL(BuildTmpProfilePath(dst.profilePath)), "def");
 }
 
 TEST_F(ArtdTest, copyAndRewriteProfileFalse) {
-  const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
-  std::string src_file = OR_FATAL(BuildTmpRefProfilePath(src));
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
   CreateFile(src_file, "abc");
   OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
   dst.profilePath.id = "";
@@ -1068,7 +1067,7 @@
 TEST_F(ArtdTest, copyAndRewriteProfileNotFound) {
   CreateFile(dex_file_);
 
-  const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
   OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
   dst.profilePath.id = "";
 
@@ -1078,8 +1077,8 @@
 }
 
 TEST_F(ArtdTest, copyAndRewriteProfileFailed) {
-  const TmpRefProfilePath& src = profile_path_->get<ProfilePath::tmpRefProfilePath>();
-  std::string src_file = OR_FATAL(BuildTmpRefProfilePath(src));
+  const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
   CreateFile(src_file, "abc");
   OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
   dst.profilePath.id = "";
@@ -1097,19 +1096,18 @@
 }
 
 TEST_F(ArtdTest, commitTmpProfile) {
-  const TmpRefProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpRefProfilePath>();
-  std::string tmp_profile_file = OR_FATAL(BuildTmpRefProfilePath(tmp_profile_path));
+  const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path));
   CreateFile(tmp_profile_file);
 
   EXPECT_TRUE(artd_->commitTmpProfile(tmp_profile_path).isOk());
 
   EXPECT_FALSE(std::filesystem::exists(tmp_profile_file));
-  EXPECT_TRUE(
-      std::filesystem::exists(OR_FATAL(BuildProfileOrDmPath(tmp_profile_path.refProfilePath))));
+  EXPECT_TRUE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
 }
 
 TEST_F(ArtdTest, commitTmpProfileFailed) {
-  const TmpRefProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpRefProfilePath>();
+  const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
   ndk::ScopedAStatus status = artd_->commitTmpProfile(tmp_profile_path);
 
   EXPECT_FALSE(status.isOk());
@@ -1118,8 +1116,7 @@
       status.getMessage(),
       ContainsRegex(R"re(Failed to move .*primary\.prof\.12345\.tmp.* to .*primary\.prof)re"));
 
-  EXPECT_FALSE(
-      std::filesystem::exists(OR_FATAL(BuildProfileOrDmPath(tmp_profile_path.refProfilePath))));
+  EXPECT_FALSE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
 }
 
 TEST_F(ArtdTest, deleteProfile) {
@@ -1231,19 +1228,18 @@
 }
 
 TEST_F(ArtdTest, mergeProfiles) {
-  const TmpRefProfilePath& reference_profile_path =
-      profile_path_->get<ProfilePath::tmpRefProfilePath>();
-  std::string reference_profile_file = OR_FATAL(BuildTmpRefProfilePath(reference_profile_path));
+  const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
   CreateFile(reference_profile_file, "abc");
 
   // Doesn't exist.
-  CurProfilePath profile_0_path{
+  PrimaryCurProfilePath profile_0_path{
       .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
-  std::string profile_0_file = OR_FATAL(BuildCurProfilePath(profile_0_path));
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
 
-  CurProfilePath profile_1_path{
+  PrimaryCurProfilePath profile_1_path{
       .userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
-  std::string profile_1_file = OR_FATAL(BuildCurProfilePath(profile_1_path));
+  std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
   CreateFile(profile_1_file, "def");
 
   OutputProfile output_profile{.profilePath = reference_profile_path,
@@ -1277,16 +1273,16 @@
                   .isOk());
   EXPECT_TRUE(result);
   EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
-  CheckContent(OR_FATAL(BuildTmpRefProfilePath(output_profile.profilePath)), "merged");
+  CheckContent(OR_FATAL(BuildTmpProfilePath(output_profile.profilePath)), "merged");
 }
 
 TEST_F(ArtdTest, mergeProfilesEmptyReferenceProfile) {
-  CurProfilePath profile_0_path{
+  PrimaryCurProfilePath profile_0_path{
       .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
-  std::string profile_0_file = OR_FATAL(BuildCurProfilePath(profile_0_path));
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
   CreateFile(profile_0_file, "def");
 
-  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpRefProfilePath>(),
+  OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(),
                                .fsPermission = FsPermission{.uid = -1, .gid = -1}};
   output_profile.profilePath.id = "";
 
@@ -1312,24 +1308,23 @@
           .isOk());
   EXPECT_TRUE(result);
   EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
-  CheckContent(OR_FATAL(BuildTmpRefProfilePath(output_profile.profilePath)), "merged");
+  CheckContent(OR_FATAL(BuildTmpProfilePath(output_profile.profilePath)), "merged");
 }
 
 TEST_F(ArtdTest, mergeProfilesProfilesDontExist) {
-  const TmpRefProfilePath& reference_profile_path =
-      profile_path_->get<ProfilePath::tmpRefProfilePath>();
-  std::string reference_profile_file = OR_FATAL(BuildTmpRefProfilePath(reference_profile_path));
+  const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
+  std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
   CreateFile(reference_profile_file, "abc");
 
   // Doesn't exist.
-  CurProfilePath profile_0_path{
+  PrimaryCurProfilePath profile_0_path{
       .userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
-  std::string profile_0_file = OR_FATAL(BuildCurProfilePath(profile_0_path));
+  std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
 
   // Doesn't exist.
-  CurProfilePath profile_1_path{
+  PrimaryCurProfilePath profile_1_path{
       .userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
-  std::string profile_1_file = OR_FATAL(BuildCurProfilePath(profile_1_path));
+  std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
 
   OutputProfile output_profile{.profilePath = reference_profile_path,
                                .fsPermission = FsPermission{.uid = -1, .gid = -1}};
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 7bed2ff..782334b 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -60,7 +60,7 @@
      *
      * Throws fatal and non-fatal errors.
      */
-    void commitTmpProfile(in com.android.server.art.ProfilePath.TmpRefProfilePath profile);
+    void commitTmpProfile(in com.android.server.art.ProfilePath.TmpProfilePath profile);
 
     /**
      * Deletes the profile. Does nothing of the profile doesn't exist.
diff --git a/artd/binder/com/android/server/art/OutputProfile.aidl b/artd/binder/com/android/server/art/OutputProfile.aidl
index cd9627b..50efda2 100644
--- a/artd/binder/com/android/server/art/OutputProfile.aidl
+++ b/artd/binder/com/android/server/art/OutputProfile.aidl
@@ -27,7 +27,7 @@
      *
      * Only outputing to a temporary file is supported to avoid race condition.
      */
-    com.android.server.art.ProfilePath.TmpRefProfilePath profilePath;
+    com.android.server.art.ProfilePath.TmpProfilePath profilePath;
 
     /** The permission of the file. */
     com.android.server.art.FsPermission fsPermission;
diff --git a/artd/binder/com/android/server/art/ProfilePath.aidl b/artd/binder/com/android/server/art/ProfilePath.aidl
index afd0fd6..fd413a9 100644
--- a/artd/binder/com/android/server/art/ProfilePath.aidl
+++ b/artd/binder/com/android/server/art/ProfilePath.aidl
@@ -22,29 +22,24 @@
  * @hide
  */
 union ProfilePath {
-    RefProfilePath refProfilePath;
-    TmpRefProfilePath tmpRefProfilePath;
+    PrimaryRefProfilePath primaryRefProfilePath;
     PrebuiltProfilePath prebuiltProfilePath;
-    CurProfilePath curProfilePath;
+    PrimaryCurProfilePath primaryCurProfilePath;
+    SecondaryRefProfilePath secondaryRefProfilePath;
+    SecondaryCurProfilePath secondaryCurProfilePath;
+    TmpProfilePath tmpProfilePath;
+
     /** Represents a profile in the dex metadata file. */
     com.android.server.art.DexMetadataPath dexMetadataPath;
 
     /** Represents a reference profile. */
-    parcelable RefProfilePath {
+    parcelable PrimaryRefProfilePath {
         /** The name of the package. */
         @utf8InCpp String packageName;
         /** The stem of the profile file */
         @utf8InCpp String profileName;
     }
 
-    /** Represents a temporary reference profile. */
-    parcelable TmpRefProfilePath {
-        /** The reference profile that this temporary file is for. */
-        RefProfilePath refProfilePath;
-        /** A unique identifier to distinguish this temporary file from others. Filled by artd. */
-        @utf8InCpp String id;
-    }
-
     /**
      * 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
@@ -57,7 +52,7 @@
     }
 
     /** Represents a current profile. */
-    parcelable CurProfilePath {
+    parcelable PrimaryCurProfilePath {
         /** The user ID of the user that owns the profile. */
         int userId;
         /** The name of the package. */
@@ -65,4 +60,35 @@
         /** The stem of the profile file */
         @utf8InCpp String profileName;
     }
+
+    /** Represents a reference profile of a secondary dex file. */
+    parcelable SecondaryRefProfilePath {
+        /**
+         * The path to the dex file that the profile is next to.
+         *
+         * Currently, possible paths are in the format of
+         * `{/data,/mnt/expand/<volume-uuid>}/{user,user_de}/<user-id>/<package-name>/...`.
+         */
+        @utf8InCpp String dexPath;
+    }
+
+    /** Represents a current profile of a secondary dex file. */
+    parcelable SecondaryCurProfilePath {
+        /** The path to the dex file that the profile is next to. */
+        @utf8InCpp String dexPath;
+    }
+
+    /** All types of profile paths that artd can write to. */
+    union WritableProfilePath {
+        PrimaryRefProfilePath forPrimary;
+        SecondaryRefProfilePath forSecondary;
+    }
+
+    /** Represents a temporary profile. */
+    parcelable TmpProfilePath {
+        /** The path that this temporary file will eventually be committed to. */
+        WritableProfilePath finalPath;
+        /** A unique identifier to distinguish this temporary file from others. Filled by artd. */
+        @utf8InCpp String id;
+    }
 }
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
index 8314241..8dcc1e9 100644
--- a/artd/path_utils.cc
+++ b/artd/path_utils.cc
@@ -43,10 +43,13 @@
 
 using ::fmt::literals::operator""_format;  // NOLINT
 
-using CurProfilePath = ProfilePath::CurProfilePath;
 using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
-using RefProfilePath = ProfilePath::RefProfilePath;
-using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
+using WritableProfilePath = ProfilePath::WritableProfilePath;
 
 Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
   if (path_str.empty()) {
@@ -145,19 +148,13 @@
   }
 }
 
-Result<std::string> BuildRefProfilePath(const RefProfilePath& ref_profile_path) {
-  OR_RETURN(ValidatePathElement(ref_profile_path.packageName, "packageName"));
-  OR_RETURN(ValidatePathElementSubstring(ref_profile_path.profileName, "profileName"));
+Result<std::string> BuildPrimaryRefProfilePath(
+    const PrimaryRefProfilePath& primary_ref_profile_path) {
+  OR_RETURN(ValidatePathElement(primary_ref_profile_path.packageName, "packageName"));
+  OR_RETURN(ValidatePathElementSubstring(primary_ref_profile_path.profileName, "profileName"));
   return "{}/misc/profiles/ref/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
-                                                  ref_profile_path.packageName,
-                                                  ref_profile_path.profileName);
-}
-
-Result<std::string> BuildTmpRefProfilePath(const TmpRefProfilePath& tmp_ref_profile_path) {
-  OR_RETURN(ValidatePathElementSubstring(tmp_ref_profile_path.id, "id"));
-  return NewFile::BuildTempPath(
-      OR_RETURN(BuildRefProfilePath(tmp_ref_profile_path.refProfilePath)).c_str(),
-      tmp_ref_profile_path.id.c_str());
+                                                  primary_ref_profile_path.packageName,
+                                                  primary_ref_profile_path.profileName);
 }
 
 Result<std::string> BuildPrebuiltProfilePath(const PrebuiltProfilePath& prebuilt_profile_path) {
@@ -165,13 +162,47 @@
   return prebuilt_profile_path.dexPath + ".prof";
 }
 
-Result<std::string> BuildCurProfilePath(const CurProfilePath& cur_profile_path) {
-  OR_RETURN(ValidatePathElement(cur_profile_path.packageName, "packageName"));
-  OR_RETURN(ValidatePathElementSubstring(cur_profile_path.profileName, "profileName"));
+Result<std::string> BuildPrimaryCurProfilePath(
+    const PrimaryCurProfilePath& primary_cur_profile_path) {
+  OR_RETURN(ValidatePathElement(primary_cur_profile_path.packageName, "packageName"));
+  OR_RETURN(ValidatePathElementSubstring(primary_cur_profile_path.profileName, "profileName"));
   return "{}/misc/profiles/cur/{}/{}/{}.prof"_format(OR_RETURN(GetAndroidDataOrError()),
-                                                     cur_profile_path.userId,
-                                                     cur_profile_path.packageName,
-                                                     cur_profile_path.profileName);
+                                                     primary_cur_profile_path.userId,
+                                                     primary_cur_profile_path.packageName,
+                                                     primary_cur_profile_path.profileName);
+}
+
+Result<std::string> BuildSecondaryRefProfilePath(
+    const SecondaryRefProfilePath& secondary_ref_profile_path) {
+  OR_RETURN(ValidateDexPath(secondary_ref_profile_path.dexPath));
+  std::filesystem::path dex_path(secondary_ref_profile_path.dexPath);
+  return "{}/oat/{}.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildSecondaryCurProfilePath(
+    const SecondaryCurProfilePath& secondary_cur_profile_path) {
+  OR_RETURN(ValidateDexPath(secondary_cur_profile_path.dexPath));
+  std::filesystem::path dex_path(secondary_cur_profile_path.dexPath);
+  return "{}/oat/{}.cur.prof"_format(dex_path.parent_path().string(), dex_path.filename().string());
+}
+
+Result<std::string> BuildFinalProfilePath(const TmpProfilePath& tmp_profile_path) {
+  const WritableProfilePath& final_path = tmp_profile_path.finalPath;
+  switch (final_path.getTag()) {
+    case WritableProfilePath::forPrimary:
+      return BuildPrimaryRefProfilePath(final_path.get<WritableProfilePath::forPrimary>());
+    case WritableProfilePath::forSecondary:
+      return BuildSecondaryRefProfilePath(final_path.get<WritableProfilePath::forSecondary>());
+      // No default. All cases should be explicitly handled, or the compilation will fail.
+  }
+  // This should never happen. Just in case we get a non-enumerator value.
+  LOG(FATAL) << "Unexpected writable profile path type {}"_format(final_path.getTag());
+}
+
+Result<std::string> BuildTmpProfilePath(const TmpProfilePath& tmp_profile_path) {
+  OR_RETURN(ValidatePathElementSubstring(tmp_profile_path.id, "id"));
+  return NewFile::BuildTempPath(OR_RETURN(BuildFinalProfilePath(tmp_profile_path)),
+                                tmp_profile_path.id);
 }
 
 Result<std::string> BuildDexMetadataPath(const DexMetadataPath& dex_metadata_path) {
@@ -186,14 +217,18 @@
 
 Result<std::string> BuildProfileOrDmPath(const ProfilePath& profile_path) {
   switch (profile_path.getTag()) {
-    case ProfilePath::refProfilePath:
-      return BuildRefProfilePath(profile_path.get<ProfilePath::refProfilePath>());
-    case ProfilePath::tmpRefProfilePath:
-      return BuildTmpRefProfilePath(profile_path.get<ProfilePath::tmpRefProfilePath>());
+    case ProfilePath::primaryRefProfilePath:
+      return BuildPrimaryRefProfilePath(profile_path.get<ProfilePath::primaryRefProfilePath>());
     case ProfilePath::prebuiltProfilePath:
       return BuildPrebuiltProfilePath(profile_path.get<ProfilePath::prebuiltProfilePath>());
-    case ProfilePath::curProfilePath:
-      return BuildCurProfilePath(profile_path.get<ProfilePath::curProfilePath>());
+    case ProfilePath::primaryCurProfilePath:
+      return BuildPrimaryCurProfilePath(profile_path.get<ProfilePath::primaryCurProfilePath>());
+    case ProfilePath::secondaryRefProfilePath:
+      return BuildSecondaryRefProfilePath(profile_path.get<ProfilePath::secondaryRefProfilePath>());
+    case ProfilePath::secondaryCurProfilePath:
+      return BuildSecondaryCurProfilePath(profile_path.get<ProfilePath::secondaryCurProfilePath>());
+    case ProfilePath::tmpProfilePath:
+      return BuildTmpProfilePath(profile_path.get<ProfilePath::tmpProfilePath>());
     case ProfilePath::dexMetadataPath:
       return BuildDexMetadataPath(profile_path.get<ProfilePath::dexMetadataPath>());
       // No default. All cases should be explicitly handled, or the compilation will fail.
diff --git a/artd/path_utils.h b/artd/path_utils.h
index 5b2b1d3..bfbbd80 100644
--- a/artd/path_utils.h
+++ b/artd/path_utils.h
@@ -42,17 +42,30 @@
   return ReplaceFileExtension(oat_path, "art");
 }
 
-android::base::Result<std::string> BuildRefProfilePath(
-    const aidl::com::android::server::art::ProfilePath::RefProfilePath& ref_profile_path);
-
-android::base::Result<std::string> BuildTmpRefProfilePath(
-    const aidl::com::android::server::art::ProfilePath::TmpRefProfilePath& tmp_ref_profile_path);
+android::base::Result<std::string> BuildPrimaryRefProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrimaryRefProfilePath&
+        primary_ref_profile_path);
 
 android::base::Result<std::string> BuildPrebuiltProfilePath(
     const aidl::com::android::server::art::ProfilePath::PrebuiltProfilePath& prebuilt_profile_path);
 
-android::base::Result<std::string> BuildCurProfilePath(
-    const aidl::com::android::server::art::ProfilePath::CurProfilePath& cur_profile_path);
+android::base::Result<std::string> BuildPrimaryCurProfilePath(
+    const aidl::com::android::server::art::ProfilePath::PrimaryCurProfilePath&
+        primary_cur_profile_path);
+
+android::base::Result<std::string> BuildSecondaryRefProfilePath(
+    const aidl::com::android::server::art::ProfilePath::SecondaryRefProfilePath&
+        secondary_ref_profile_path);
+
+android::base::Result<std::string> BuildSecondaryCurProfilePath(
+    const aidl::com::android::server::art::ProfilePath::SecondaryCurProfilePath&
+        secondary_cur_profile_path);
+
+android::base::Result<std::string> BuildFinalProfilePath(
+    const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
+
+android::base::Result<std::string> BuildTmpProfilePath(
+    const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path);
 
 android::base::Result<std::string> BuildDexMetadataPath(
     const aidl::com::android::server::art::DexMetadataPath& dex_metadata_path);
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
index 8188000..508d8c2 100644
--- a/artd/path_utils_test.cc
+++ b/artd/path_utils_test.cc
@@ -33,10 +33,12 @@
 using ::android::base::testing::HasValue;
 using ::android::base::testing::WithMessage;
 
-using CurProfilePath = ProfilePath::CurProfilePath;
 using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
-using RefProfilePath = ProfilePath::RefProfilePath;
-using TmpRefProfilePath = ProfilePath::TmpRefProfilePath;
+using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
+using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
+using SecondaryCurProfilePath = ProfilePath::SecondaryCurProfilePath;
+using SecondaryRefProfilePath = ProfilePath::SecondaryRefProfilePath;
+using TmpProfilePath = ProfilePath::TmpProfilePath;
 
 using std::literals::operator""s;  // NOLINT
 
@@ -104,80 +106,110 @@
   EXPECT_EQ(OatPathToArtPath("/a/oat/arm64/b.odex"), "/a/oat/arm64/b.art");
 }
 
-TEST_F(PathUtilsTest, BuildRefProfilePath) {
-  EXPECT_THAT(BuildRefProfilePath(
-                  RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}),
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePath) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                               .profileName = "primary"}),
               HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
 }
 
-TEST_F(PathUtilsTest, BuildRefProfilePathPackageNameOk) {
-  EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "...", .profileName = "primary"}),
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameOk) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "...", .profileName = "primary"}),
               HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof"));
-  EXPECT_THAT(BuildRefProfilePath(
-                  RefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}),
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}),
               HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof"));
 }
 
-TEST_F(PathUtilsTest, BuildRefProfilePathPackageNameWrong) {
-  EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "", .profileName = "primary"}),
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameWrong) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "", .profileName = "primary"}),
               HasError(WithMessage("packageName is empty")));
-  EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = ".", .profileName = "primary"}),
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = ".", .profileName = "primary"}),
               HasError(WithMessage("Invalid packageName '.'")));
-  EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "..", .profileName = "primary"}),
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "..", .profileName = "primary"}),
               HasError(WithMessage("Invalid packageName '..'")));
-  EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "a/b", .profileName = "primary"}),
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "a/b", .profileName = "primary"}),
               HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
-  EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}),
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}),
               HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
 }
 
-TEST_F(PathUtilsTest, BuildRefProfilePathProfileNameOk) {
-  EXPECT_THAT(
-      BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = "."}),
-      HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof"));
-  EXPECT_THAT(
-      BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = ".."}),
-      HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof"));
-  EXPECT_THAT(BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo",
-                                                 .profileName = "!@#$%^&*()_+-="}),
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameOk) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "."}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ".."}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof"));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                               .profileName = "!@#$%^&*()_+-="}),
               HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof"));
 }
 
-TEST_F(PathUtilsTest, BuildRefProfilePathProfileNameWrong) {
-  EXPECT_THAT(
-      BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = ""}),
-      HasError(WithMessage("profileName is empty")));
-  EXPECT_THAT(
-      BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}),
-      HasError(WithMessage("profileName 'a/b' has invalid character '/'")));
-  EXPECT_THAT(
-      BuildRefProfilePath(RefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}),
-      HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s)));
+TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameWrong) {
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ""}),
+              HasError(WithMessage("profileName is empty")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}),
+              HasError(WithMessage("profileName 'a/b' has invalid character '/'")));
+  EXPECT_THAT(BuildPrimaryRefProfilePath(
+                  PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}),
+              HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s)));
 }
 
-TEST_F(PathUtilsTest, BuildTmpRefProfilePath) {
+TEST_F(PathUtilsTest, BuildFinalProfilePathForPrimary) {
+  EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildFinalProfilePathForSecondary) {
+  EXPECT_THAT(BuildFinalProfilePath(TmpProfilePath{
+                  .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+                                                                  "/user/0/com.android.foo/a.apk"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathForPrimary) {
   EXPECT_THAT(
-      BuildTmpRefProfilePath(TmpRefProfilePath{
-          .refProfilePath =
-              RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+      BuildTmpProfilePath(TmpProfilePath{
+          .finalPath =
+              PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
           .id = "12345"}),
       HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
 }
 
-TEST_F(PathUtilsTest, BuildTmpRefProfilePathIdWrong) {
-  EXPECT_THAT(BuildTmpRefProfilePath(TmpRefProfilePath{
-                  .refProfilePath =
-                      RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+TEST_F(PathUtilsTest, BuildTmpProfilePathForSecondary) {
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = SecondaryRefProfilePath{.dexPath = android_data_ +
+                                                                  "/user/0/com.android.foo/a.apk"},
+                  .id = "12345"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof.12345.tmp"));
+}
+
+TEST_F(PathUtilsTest, BuildTmpProfilePathIdWrong) {
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
                   .id = ""}),
               HasError(WithMessage("id is empty")));
-  EXPECT_THAT(BuildTmpRefProfilePath(TmpRefProfilePath{
-                  .refProfilePath =
-                      RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
                   .id = "123/45"}),
               HasError(WithMessage("id '123/45' has invalid character '/'")));
-  EXPECT_THAT(BuildTmpRefProfilePath(TmpRefProfilePath{
-                  .refProfilePath =
-                      RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+  EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
+                  .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                     .profileName = "primary"},
                   .id = "123\0a"s}),
               HasError(WithMessage("id '123\0a' has invalid character '\\0'"s)));
 }
@@ -187,12 +219,24 @@
               HasValue("/a/b.apk.prof"));
 }
 
-TEST_F(PathUtilsTest, BuildCurProfilePath) {
-  EXPECT_THAT(BuildCurProfilePath(CurProfilePath{
+TEST_F(PathUtilsTest, BuildPrimaryCurProfilePath) {
+  EXPECT_THAT(BuildPrimaryCurProfilePath(PrimaryCurProfilePath{
                   .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
               HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
 }
 
+TEST_F(PathUtilsTest, BuildSecondaryRefProfilePath) {
+  EXPECT_THAT(BuildSecondaryRefProfilePath(SecondaryRefProfilePath{
+                  .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.prof"));
+}
+
+TEST_F(PathUtilsTest, BuildSecondaryCurProfilePath) {
+  EXPECT_THAT(BuildSecondaryCurProfilePath(SecondaryCurProfilePath{
+                  .dexPath = android_data_ + "/user/0/com.android.foo/a.apk"}),
+              HasValue(android_data_ + "/user/0/com.android.foo/oat/a.apk.cur.prof"));
+}
+
 TEST_F(PathUtilsTest, BuildDexMetadataPath) {
   EXPECT_THAT(BuildDexMetadataPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
 }
@@ -203,18 +247,18 @@
 }
 
 TEST_F(PathUtilsTest, BuildProfilePath) {
-  EXPECT_THAT(BuildProfileOrDmPath(
-                  RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}),
+  EXPECT_THAT(BuildProfileOrDmPath(PrimaryRefProfilePath{.packageName = "com.android.foo",
+                                                         .profileName = "primary"}),
               HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"));
   EXPECT_THAT(
-      BuildProfileOrDmPath(TmpRefProfilePath{
-          .refProfilePath =
-              RefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
+      BuildProfileOrDmPath(TmpProfilePath{
+          .finalPath =
+              PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
           .id = "12345"}),
       HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"));
   EXPECT_THAT(BuildProfileOrDmPath(PrebuiltProfilePath{.dexPath = "/a/b.apk"}),
               HasValue("/a/b.apk.prof"));
-  EXPECT_THAT(BuildProfileOrDmPath(CurProfilePath{
+  EXPECT_THAT(BuildProfileOrDmPath(PrimaryCurProfilePath{
                   .userId = 1, .packageName = "com.android.foo", .profileName = "primary"}),
               HasValue(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"));
   EXPECT_THAT(BuildProfileOrDmPath(DexMetadataPath{.dexPath = "/a/b.apk"}), HasValue("/a/b.dm"));
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
index 26d5df8..f7df305 100644
--- a/libartservice/service/java/com/android/server/art/AidlUtils.java
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -18,10 +18,13 @@
 
 import static com.android.server.art.OutputArtifacts.PermissionSettings;
 import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
-import static com.android.server.art.ProfilePath.CurProfilePath;
 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 static com.android.server.art.ProfilePath.PrimaryCurProfilePath;
+import static com.android.server.art.ProfilePath.PrimaryRefProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryCurProfilePath;
+import static com.android.server.art.ProfilePath.SecondaryRefProfilePath;
+import static com.android.server.art.ProfilePath.TmpProfilePath;
+import static com.android.server.art.ProfilePath.WritableProfilePath;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -83,18 +86,26 @@
     }
 
     @NonNull
-    public static RefProfilePath buildRefProfilePath(
+    public static PrimaryRefProfilePath buildPrimaryRefProfilePath(
             @NonNull String packageName, @NonNull String profileName) {
-        var refProfilePath = new RefProfilePath();
-        refProfilePath.packageName = packageName;
-        refProfilePath.profileName = profileName;
-        return refProfilePath;
+        var primaryRefProfilePath = new PrimaryRefProfilePath();
+        primaryRefProfilePath.packageName = packageName;
+        primaryRefProfilePath.profileName = profileName;
+        return primaryRefProfilePath;
     }
 
     @NonNull
-    public static ProfilePath buildProfilePathForRef(
+    public static SecondaryRefProfilePath buildSecondaryRefProfilePath(@NonNull String dexPath) {
+        var secondaryRefProfilePath = new SecondaryRefProfilePath();
+        secondaryRefProfilePath.dexPath = dexPath;
+        return secondaryRefProfilePath;
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForPrimaryRef(
             @NonNull String packageName, @NonNull String profileName) {
-        return ProfilePath.refProfilePath(buildRefProfilePath(packageName, profileName));
+        return ProfilePath.primaryRefProfilePath(
+                buildPrimaryRefProfilePath(packageName, profileName));
     }
 
     @NonNull
@@ -110,23 +121,89 @@
     }
 
     @NonNull
-    public static ProfilePath buildProfilePathForCur(
+    public static ProfilePath buildProfilePathForPrimaryCur(
             int userId, @NonNull String packageName, @NonNull String profileName) {
-        var curProfilePath = new CurProfilePath();
-        curProfilePath.userId = userId;
-        curProfilePath.packageName = packageName;
-        curProfilePath.profileName = profileName;
-        return ProfilePath.curProfilePath(curProfilePath);
+        var primaryCurProfilePath = new PrimaryCurProfilePath();
+        primaryCurProfilePath.userId = userId;
+        primaryCurProfilePath.packageName = packageName;
+        primaryCurProfilePath.profileName = profileName;
+        return ProfilePath.primaryCurProfilePath(primaryCurProfilePath);
     }
 
     @NonNull
-    public static OutputProfile buildOutputProfile(@NonNull String packageName,
-            @NonNull String profileName, int uid, int gid, boolean isPublic) {
+    public static ProfilePath buildProfilePathForSecondaryRef(@NonNull String dexPath) {
+        return ProfilePath.secondaryRefProfilePath(buildSecondaryRefProfilePath(dexPath));
+    }
+
+    @NonNull
+    public static ProfilePath buildProfilePathForSecondaryCur(@NonNull String dexPath) {
+        var secondaryCurProfilePath = new SecondaryCurProfilePath();
+        secondaryCurProfilePath.dexPath = dexPath;
+        return ProfilePath.secondaryCurProfilePath(secondaryCurProfilePath);
+    }
+
+    @NonNull
+    private static OutputProfile buildOutputProfile(
+            @NonNull WritableProfilePath finalPath, int uid, int gid, boolean isPublic) {
         var outputProfile = new OutputProfile();
-        outputProfile.profilePath = new TmpRefProfilePath();
-        outputProfile.profilePath.refProfilePath = buildRefProfilePath(packageName, profileName);
+        outputProfile.profilePath = new TmpProfilePath();
+        outputProfile.profilePath.finalPath = finalPath;
         outputProfile.profilePath.id = ""; // Will be filled by artd.
         outputProfile.fsPermission = buildFsPermission(uid, gid, isPublic);
         return outputProfile;
     }
+
+    @NonNull
+    public static OutputProfile buildOutputProfileForPrimary(@NonNull String packageName,
+            @NonNull String profileName, int uid, int gid, boolean isPublic) {
+        return buildOutputProfile(WritableProfilePath.forPrimary(
+                                          buildPrimaryRefProfilePath(packageName, profileName)),
+                uid, gid, isPublic);
+    }
+
+    @NonNull
+    public static OutputProfile buildOutputProfileForSecondary(
+            @NonNull String dexPath, int uid, int gid, boolean isPublic) {
+        return buildOutputProfile(
+                WritableProfilePath.forSecondary(buildSecondaryRefProfilePath(dexPath)), uid, gid,
+                isPublic);
+    }
+
+    @NonNull
+    public static String toString(@NonNull PrimaryRefProfilePath profile) {
+        return String.format(
+                "[packageName = %s, profileName = %s]", profile.packageName, profile.profileName);
+    }
+
+    @NonNull
+    public static String toString(@NonNull SecondaryRefProfilePath profile) {
+        return String.format("[dexPath = %s]", profile.dexPath);
+    }
+
+    @NonNull
+    public static String toString(@NonNull WritableProfilePath profile) {
+        switch (profile.getTag()) {
+            case WritableProfilePath.forPrimary:
+                return toString(profile.getForPrimary());
+            case WritableProfilePath.forSecondary:
+                return toString(profile.getForSecondary());
+            default:
+                throw new IllegalStateException(
+                        "Unknown WritableProfilePath tag " + profile.getTag());
+        }
+    }
+
+    @NonNull
+    public static String toString(@NonNull ProfilePath profile) {
+        switch (profile.getTag()) {
+            case ProfilePath.primaryRefProfilePath:
+                return toString(profile.getPrimaryRefProfilePath());
+            case ProfilePath.secondaryRefProfilePath:
+                return toString(profile.getSecondaryRefProfilePath());
+            default:
+                throw new UnsupportedOperationException(
+                        "Only reference profile paths are supported to be converted to string, got "
+                        + profile.getTag());
+        }
+    }
 }
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
index a80bb05..ef7d659 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
@@ -20,8 +20,8 @@
 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.ProfilePath.TmpProfilePath;
+import static com.android.server.art.ProfilePath.WritableProfilePath;
 import static com.android.server.art.Utils.Abi;
 import static com.android.server.art.model.ArtFlags.OptimizeFlags;
 import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
@@ -134,8 +134,7 @@
                         ProfilePath mergedProfile =
                                 mergeProfiles(pkgState, dexInfo, appId, sharedGid, profile);
                         if (mergedProfile != null) {
-                            if (profile != null
-                                    && profile.getTag() == ProfilePath.tmpRefProfilePath) {
+                            if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
                                 mInjector.getArtd().deleteProfile(profile);
                             }
                             profile = mergedProfile;
@@ -236,9 +235,9 @@
                 }
 
                 if (profile != null && succeeded) {
-                    if (profile.getTag() == ProfilePath.tmpRefProfilePath) {
+                    if (profile.getTag() == ProfilePath.tmpProfilePath) {
                         // Commit the profile only if dexopt succeeds.
-                        if (commitProfileChanges(profile.getTmpRefProfilePath())) {
+                        if (commitProfileChanges(profile.getTmpProfilePath())) {
                             profile = null;
                         }
                     }
@@ -256,7 +255,7 @@
                     }
                 }
             } finally {
-                if (profile != null && profile.getTag() == ProfilePath.tmpRefProfilePath) {
+                if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
                     mInjector.getArtd().deleteProfile(profile);
                 }
             }
@@ -315,7 +314,7 @@
     private ProfilePath initReferenceProfile(@NonNull PackageState pkgState,
             @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid) throws RemoteException {
         String profileName = getProfileName(dexInfo.splitName());
-        OutputProfile output = AidlUtils.buildOutputProfile(
+        OutputProfile output = AidlUtils.buildOutputProfileForPrimary(
                 pkgState.getPackageName(), profileName, uid, gid, true /* isPublic */);
 
         ProfilePath prebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath());
@@ -327,26 +326,24 @@
             // case is necessary.
             if (mInjector.getArtd().copyAndRewriteProfile(
                         prebuiltProfile, output, dexInfo.dexPath())) {
-                return ProfilePath.tmpRefProfilePath(output.profilePath);
+                return ProfilePath.tmpProfilePath(output.profilePath);
             }
         } catch (ServiceSpecificException e) {
             Log.e(TAG,
-                    String.format(
-                            "Failed to use prebuilt profile [packageName = %s, profileName = %s]",
-                            pkgState.getPackageName(), profileName),
+                    "Failed to use prebuilt profile "
+                            + AidlUtils.toString(output.profilePath.finalPath),
                     e);
         }
 
         ProfilePath dmProfile = AidlUtils.buildProfilePathForDm(dexInfo.dexPath());
         try {
             if (mInjector.getArtd().copyAndRewriteProfile(dmProfile, output, dexInfo.dexPath())) {
-                return ProfilePath.tmpRefProfilePath(output.profilePath);
+                return ProfilePath.tmpProfilePath(output.profilePath);
             }
         } catch (ServiceSpecificException e) {
             Log.e(TAG,
-                    String.format("Failed to use profile in dex metadata file "
-                                    + "[packageName = %s, profileName = %s]",
-                            pkgState.getPackageName(), profileName),
+                    "Failed to use profile in dex metadata file "
+                            + AidlUtils.toString(output.profilePath.finalPath),
                     e);
         }
 
@@ -366,7 +363,7 @@
             @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid) throws RemoteException {
         String profileName = getProfileName(dexInfo.splitName());
         ProfilePath refProfile =
-                AidlUtils.buildProfilePathForRef(pkgState.getPackageName(), profileName);
+                AidlUtils.buildProfilePathForPrimaryRef(pkgState.getPackageName(), profileName);
         try {
             if (mInjector.getArtd().isProfileUsable(refProfile, dexInfo.dexPath())) {
                 boolean isOtherReadable = mInjector.getArtd().getProfileVisibility(refProfile)
@@ -375,9 +372,8 @@
             }
         } catch (ServiceSpecificException e) {
             Log.e(TAG,
-                    String.format("Failed to use the existing reference profile "
-                                    + "[packageName = %s, profileName = %s]",
-                            pkgState.getPackageName(), profileName),
+                    "Failed to use the existing reference profile "
+                            + AidlUtils.toString(refProfile),
                     e);
         }
 
@@ -529,17 +525,12 @@
         }
     }
 
-    private boolean commitProfileChanges(@NonNull TmpRefProfilePath profile)
-            throws RemoteException {
+    private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
         try {
             mInjector.getArtd().commitTmpProfile(profile);
             return true;
         } catch (ServiceSpecificException e) {
-            RefProfilePath refProfilePath = profile.refProfilePath;
-            Log.e(TAG,
-                    String.format(
-                            "Failed to commit profile changes [packageName = %s, profileName = %s]",
-                            refProfilePath.packageName, refProfilePath.profileName),
+            Log.e(TAG, "Failed to commit profile changes " + AidlUtils.toString(profile.finalPath),
                     e);
             return false;
         }
@@ -550,18 +541,17 @@
             @NonNull DetailedPrimaryDexInfo dexInfo, int uid, int gid,
             @Nullable ProfilePath referenceProfile) throws RemoteException {
         String profileName = getProfileName(dexInfo.splitName());
-        OutputProfile output = AidlUtils.buildOutputProfile(
+        OutputProfile output = AidlUtils.buildOutputProfileForPrimary(
                 pkgState.getPackageName(), profileName, uid, gid, false /* isPublic */);
 
         try {
             if (mInjector.getArtd().mergeProfiles(getCurProfiles(pkgState, dexInfo),
                         referenceProfile, output, dexInfo.dexPath())) {
-                return ProfilePath.tmpRefProfilePath(output.profilePath);
+                return ProfilePath.tmpProfilePath(output.profilePath);
             }
         } catch (ServiceSpecificException e) {
             Log.e(TAG,
-                    String.format("Failed to merge profiles [packageName = %s, profileName = %s]",
-                            pkgState.getPackageName(), getProfileName(dexInfo.splitName())),
+                    "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
                     e);
         }
 
@@ -584,7 +574,7 @@
             int userId = handle.getIdentifier();
             PackageUserState userState = pkgState.getStateForUser(handle);
             if (userState.isInstalled()) {
-                profiles.add(AidlUtils.buildProfilePathForCur(
+                profiles.add(AidlUtils.buildProfilePathForPrimaryCur(
                         userId, pkgState.getPackageName(), getProfileName(dexInfo.splitName())));
             }
         }
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
index 2eae691..504be90 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -68,17 +68,18 @@
             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 mRefProfile =
+            AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary");
     private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
     private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
-    private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfile(
+    private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary(
             PKG_NAME, "primary", UID, SHARED_GID, true /* isOtherReadable */);
-    private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfile(
+    private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary(
             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");
+            AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split");
 
     private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
@@ -232,9 +233,9 @@
                 deepEq(mPrebuiltProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
 
         checkDexoptWithPublicProfile(inOrder.verify(mArtd), mDexPath, "arm64",
-                ProfilePath.tmpRefProfilePath(mPublicOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath));
         checkDexoptWithPublicProfile(inOrder.verify(mArtd), mDexPath, "arm",
-                ProfilePath.tmpRefProfilePath(mPublicOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath));
 
         inOrder.verify(mArtd).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
 
@@ -258,28 +259,29 @@
         InOrder inOrder = inOrder(mArtd);
 
         inOrder.verify(mArtd).mergeProfiles(
-                deepEq(List.of(
-                        AidlUtils.buildProfilePathForCur(0 /* userId */, PKG_NAME, "primary"),
-                        AidlUtils.buildProfilePathForCur(2 /* userId */, PKG_NAME, "primary"))),
+                deepEq(List.of(AidlUtils.buildProfilePathForPrimaryCur(
+                                       0 /* userId */, PKG_NAME, "primary"),
+                        AidlUtils.buildProfilePathForPrimaryCur(
+                                2 /* userId */, PKG_NAME, "primary"))),
                 deepEq(mRefProfile), deepEq(mPrivateOutputProfile), eq(mDexPath));
 
         // It should use `mBetterOrSameDexoptTrigger` and the merged profile for both ISAs.
         inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), eq("speed-profile"),
                 eq(mBetterOrSameDexoptTrigger));
         checkDexoptWithPrivateProfile(inOrder.verify(mArtd), mDexPath, "arm64",
-                ProfilePath.tmpRefProfilePath(mPrivateOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath));
 
         inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm"), any(), eq("speed-profile"),
                 eq(mBetterOrSameDexoptTrigger));
         checkDexoptWithPrivateProfile(inOrder.verify(mArtd), mDexPath, "arm",
-                ProfilePath.tmpRefProfilePath(mPrivateOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath));
 
         inOrder.verify(mArtd).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath));
 
-        inOrder.verify(mArtd).deleteProfile(
-                deepEq(AidlUtils.buildProfilePathForCur(0 /* userId */, PKG_NAME, "primary")));
-        inOrder.verify(mArtd).deleteProfile(
-                deepEq(AidlUtils.buildProfilePathForCur(2 /* userId */, PKG_NAME, "primary")));
+        inOrder.verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary")));
+        inOrder.verify(mArtd).deleteProfile(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(2 /* userId */, PKG_NAME, "primary")));
     }
 
     @Test
@@ -320,9 +322,9 @@
                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
 
         checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm64",
-                ProfilePath.tmpRefProfilePath(mPublicOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath));
         checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm",
-                ProfilePath.tmpRefProfilePath(mPublicOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath));
 
         verifyProfileNotUsed(mRefProfile);
         verifyProfileNotUsed(mPrebuiltProfile);
@@ -341,7 +343,7 @@
         mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
 
         verify(mArtd).deleteProfile(
-                deepEq(ProfilePath.tmpRefProfilePath(mPublicOutputProfile.profilePath)));
+                deepEq(ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath)));
         verify(mArtd, never()).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
     }
 
@@ -372,12 +374,12 @@
         verify(mArtd).getDexoptNeeded(
                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
         checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm64",
-                ProfilePath.tmpRefProfilePath(mPublicOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath));
 
         verify(mArtd).getDexoptNeeded(
                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
         checkDexoptWithPublicProfile(verify(mArtd), mDexPath, "arm",
-                ProfilePath.tmpRefProfilePath(mPublicOutputProfile.profilePath));
+                ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath));
 
         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "speed");
         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "speed");