OverlayManager Fabricated RROs

Adds registering and unregistering of FabricatedOverlay to the OMS.
The process that creates the fabricated overlays owns it and is the
only process allowed to unregister it.

When a fabricated overlay is registered, overlay settings for it are
initialized in all users. When a fabricated overlay is unregistered,
it is disabled and removed from all users. When a new user is created,
it will be able to use the fabricated overlay as well.

On boot, fabricated overlays that are not referenced in overlay
settings will be deleted.

When the package that created the fabricated overlay is uninstalled,
its fabricated overlays are also unregistered.

Bug: 172471315
Test: atest OverlayDeviceTests
Change-Id: I0539656f4c919246b13129579b0286c08a398dc2
diff --git a/Android.bp b/Android.bp
index 9655daf..cf3f90a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -369,6 +369,7 @@
         ":framework_native_aidl",
         ":gatekeeper_aidl",
         ":gsiservice_aidl",
+        ":idmap2_core_aidl",
         ":incidentcompanion_aidl",
         ":inputconstants_aidl",
         ":installd_aidl",
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 34d38d2..3bad889 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -274,6 +274,7 @@
         "libziparchive",
     ],
     static_libs: [
+        "libc++fs",
         "libidmap2_protos",
         "libidmap2daidl",
     ],
@@ -282,28 +283,41 @@
 
 cc_library_static {
     name: "libidmap2daidl",
-    defaults: [
-        "idmap2_defaults",
-    ],
-    tidy: false,
-    host_supported: false,
     srcs: [
         ":idmap2_aidl",
+        ":idmap2_core_aidl",
+    ],
+    header_libs: [
+        "libbinder_headers",
     ],
     shared_libs: [
         "libbase",
     ],
     aidl: {
         export_aidl_headers: true,
+        local_include_dirs: [
+            "idmap2d/aidl/core",
+            "idmap2d/aidl/services/",
+        ],
     },
 }
 
 filegroup {
+    name: "idmap2_core_aidl",
+    srcs: [
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl",
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl",
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl",
+    ],
+    path: "idmap2d/aidl/core/",
+}
+
+filegroup {
     name: "idmap2_aidl",
     srcs: [
-        "idmap2d/aidl/android/os/IIdmap2.aidl",
+        "idmap2d/aidl/services/android/os/IIdmap2.aidl",
     ],
-    path: "idmap2d/aidl",
+    path: "idmap2d/aidl/services/",
 }
 
 aidl_interface {
@@ -315,7 +329,7 @@
 filegroup {
     name: "overlayable_policy_aidl_files",
     srcs: [
-        "idmap2d/aidl/android/os/OverlayablePolicy.aidl",
+        "idmap2d/aidl/services/android/os/OverlayablePolicy.aidl",
     ],
-    path: "idmap2d/aidl",
+    path: "idmap2d/aidl/services/",
 }
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index f62630e..312f4ac 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -18,10 +18,10 @@
 
 #include <sys/stat.h>   // umask
 #include <sys/types.h>  // umask
-#include <unistd.h>
 
 #include <cerrno>
 #include <cstring>
+#include <filesystem>
 #include <fstream>
 #include <memory>
 #include <ostream>
@@ -35,18 +35,20 @@
 #include "idmap2/Idmap.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
-#include "utils/String8.h"
 
 using android::IPCThreadState;
 using android::base::StringPrintf;
 using android::binder::Status;
 using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::FabricatedOverlay;
+using android::idmap2::FabricatedOverlayContainer;
 using android::idmap2::Idmap;
 using android::idmap2::IdmapHeader;
 using android::idmap2::OverlayResourceContainer;
 using android::idmap2::TargetResourceContainer;
 using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::RandomStringForPath;
 using android::idmap2::utils::UidHasWriteAccessToPath;
 
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
@@ -67,6 +69,7 @@
 PolicyBitmask ConvertAidlArgToPolicyBitmask(int32_t arg) {
   return static_cast<PolicyBitmask>(arg);
 }
+
 }  // namespace
 
 namespace android::os {
@@ -99,8 +102,9 @@
 }
 
 Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::string& overlay_path,
-                                  int32_t fulfilled_policies, bool enforce_overlayable,
-                                  int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+                                  const std::string& overlay_name, int32_t fulfilled_policies,
+                                  bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
+                                  bool* _aidl_return) {
   SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_path;
   assert(_aidl_return);
 
@@ -128,9 +132,8 @@
     return ok();
   }
 
-  // TODO(162841629): Support passing overlay name to idmap2d verify
   auto up_to_date =
-      header->IsUpToDate(*GetPointer(*target), **overlay, "",
+      header->IsUpToDate(*GetPointer(*target), **overlay, overlay_name,
                          ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
 
   *_aidl_return = static_cast<bool>(up_to_date);
@@ -142,8 +145,8 @@
 }
 
 Status Idmap2Service::createIdmap(const std::string& target_path, const std::string& overlay_path,
-                                  int32_t fulfilled_policies, bool enforce_overlayable,
-                                  int32_t user_id ATTRIBUTE_UNUSED,
+                                  const std::string& overlay_name, int32_t fulfilled_policies,
+                                  bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
                                   std::optional<std::string>* _aidl_return) {
   assert(_aidl_return);
   SYSTRACE << "Idmap2Service::createIdmap " << target_path << " " << overlay_path;
@@ -168,9 +171,8 @@
     return error("failed to load apk overlay '%s'" + overlay_path);
   }
 
-  // TODO(162841629): Support passing overlay name to idmap2d create
-  const auto idmap = Idmap::FromContainers(*GetPointer(*target), **overlay, "", policy_bitmask,
-                                           enforce_overlayable);
+  const auto idmap = Idmap::FromContainers(*GetPointer(*target), **overlay, overlay_name,
+                                           policy_bitmask, enforce_overlayable);
   if (!idmap) {
     return error(idmap.GetErrorMessage());
   }
@@ -218,4 +220,134 @@
   return {std::move(*target)};
 }
 
+Status Idmap2Service::createFabricatedOverlay(
+    const os::FabricatedOverlayInternal& overlay,
+    std::optional<os::FabricatedOverlayInfo>* _aidl_return) {
+  idmap2::FabricatedOverlay::Builder builder(overlay.packageName, overlay.overlayName,
+                                             overlay.targetPackageName);
+  if (!overlay.targetOverlayable.empty()) {
+    builder.SetOverlayable(overlay.targetOverlayable);
+  }
+
+  for (const auto& res : overlay.entries) {
+    builder.SetResourceValue(res.resourceName, res.dataType, res.data);
+  }
+
+  // Generate the file path of the fabricated overlay and ensure it does not collide with an
+  // existing path. Re-registering a fabricated overlay will always result in an updated path.
+  std::string path;
+  std::string file_name;
+  do {
+    constexpr size_t kSuffixLength = 4;
+    const std::string random_suffix = RandomStringForPath(kSuffixLength);
+    file_name = StringPrintf("%s-%s-%s.frro", overlay.packageName.c_str(),
+                             overlay.overlayName.c_str(), random_suffix.c_str());
+    path = StringPrintf("%s/%s", kIdmapCacheDir, file_name.c_str());
+
+    // Invoking std::filesystem::exists with a file name greater than 255 characters will cause this
+    // process to abort since the name exceeds the maximum file name size.
+    const size_t kMaxFileNameLength = 255;
+    if (file_name.size() > kMaxFileNameLength) {
+      return error(
+          base::StringPrintf("fabricated overlay file name '%s' longer than %zu characters",
+                             file_name.c_str(), kMaxFileNameLength));
+    }
+  } while (std::filesystem::exists(path));
+
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
+  if (!UidHasWriteAccessToPath(uid, path)) {
+    return error(base::StringPrintf("will not write to %s: calling uid %d lacks write access",
+                                    path.c_str(), uid));
+  }
+
+  // Persist the fabricated overlay.
+  umask(kIdmapFilePermissionMask);
+  std::ofstream fout(path);
+  if (fout.fail()) {
+    return error("failed to open frro path " + path);
+  }
+  const auto frro = builder.Build();
+  if (!frro) {
+    return error(StringPrintf("failed to serialize '%s:%s': %s", overlay.packageName.c_str(),
+                              overlay.overlayName.c_str(), frro.GetErrorMessage().c_str()));
+  }
+  auto result = frro->ToBinaryStream(fout);
+  if (!result) {
+    unlink(path.c_str());
+    return error("failed to write to frro path " + path + ": " + result.GetErrorMessage());
+  }
+  if (fout.fail()) {
+    unlink(path.c_str());
+    return error("failed to write to frro path " + path);
+  }
+
+  os::FabricatedOverlayInfo out_info;
+  out_info.packageName = overlay.packageName;
+  out_info.overlayName = overlay.overlayName;
+  out_info.targetPackageName = overlay.targetPackageName;
+  out_info.targetOverlayable = overlay.targetOverlayable;
+  out_info.path = path;
+  *_aidl_return = out_info;
+  return ok();
+}
+
+Status Idmap2Service::getFabricatedOverlayInfos(
+    std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
+  for (const auto& entry : std::filesystem::directory_iterator(kIdmapCacheDir)) {
+    if (!android::IsFabricatedOverlay(entry.path())) {
+      continue;
+    }
+
+    const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
+    if (!overlay) {
+      // This is a sign something went wrong.
+      LOG(ERROR) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
+      continue;
+    }
+
+    const auto info = (*overlay)->GetManifestInfo();
+    os::FabricatedOverlayInfo out_info;
+    out_info.packageName = info.package_name;
+    out_info.overlayName = info.name;
+    out_info.targetPackageName = info.target_package;
+    out_info.targetOverlayable = info.target_name;
+    out_info.path = entry.path();
+    _aidl_return->emplace_back(std::move(out_info));
+  }
+
+  return ok();
+}
+
+binder::Status Idmap2Service::deleteFabricatedOverlay(const std::string& overlay_path,
+                                                      bool* _aidl_return) {
+  SYSTRACE << "Idmap2Service::deleteFabricatedOverlay " << overlay_path;
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
+
+  if (!UidHasWriteAccessToPath(uid, overlay_path)) {
+    *_aidl_return = false;
+    return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+                                    overlay_path.c_str(), uid));
+  }
+
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
+  if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+    *_aidl_return = false;
+    return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+                                    idmap_path.c_str(), uid));
+  }
+
+  if (unlink(overlay_path.c_str()) != 0) {
+    *_aidl_return = false;
+    return error("failed to unlink " + overlay_path + ": " + strerror(errno));
+  }
+
+  if (unlink(idmap_path.c_str()) != 0) {
+    *_aidl_return = false;
+    return error("failed to unlink " + idmap_path + ": " + strerror(errno));
+  }
+
+  *_aidl_return = true;
+  return ok();
+}
+
 }  // namespace android::os
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index 869a4d9..4d16ff3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -19,6 +19,7 @@
 
 #include <android-base/unique_fd.h>
 #include <android/os/BnIdmap2.h>
+#include <android/os/FabricatedOverlayInfo.h>
 #include <binder/BinderService.h>
 #include <idmap2/ResourceContainer.h>
 #include <idmap2/Result.h>
@@ -40,18 +41,32 @@
                              bool* _aidl_return) override;
 
   binder::Status verifyIdmap(const std::string& target_path, const std::string& overlay_path,
-                             int32_t fulfilled_policies, bool enforce_overlayable, int32_t user_id,
+                             const std::string& overlay_name, int32_t fulfilled_policies,
+                             bool enforce_overlayable, int32_t user_id,
                              bool* _aidl_return) override;
 
   binder::Status createIdmap(const std::string& target_path, const std::string& overlay_path,
-                             int32_t fulfilled_policies, bool enforce_overlayable, int32_t user_id,
+                             const std::string& overlay_name, int32_t fulfilled_policies,
+                             bool enforce_overlayable, int32_t user_id,
                              std::optional<std::string>* _aidl_return) override;
 
+  binder::Status createFabricatedOverlay(
+      const os::FabricatedOverlayInternal& overlay,
+      std::optional<os::FabricatedOverlayInfo>* _aidl_return) override;
+
+  binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
+                                         bool* _aidl_return) override;
+
+  binder::Status getFabricatedOverlayInfos(
+      std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
+
  private:
   // idmap2d is killed after a period of inactivity, so any information stored on this class should
   // be able to be recalculated if idmap2 dies and restarts.
   std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
 
+  std::vector<os::FabricatedOverlayInfo> fabricated_overlays_;
+
   template <typename T>
   using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
 
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
new file mode 100644
index 0000000..6375d24
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInfo {
+    @utf8InCpp String path;
+    @utf8InCpp String packageName;
+    @utf8InCpp String overlayName;
+    @utf8InCpp String targetPackageName;
+    @utf8InCpp String targetOverlayable;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
new file mode 100644
index 0000000..f67d8be
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.FabricatedOverlayInternalEntry;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternal {
+    @utf8InCpp String packageName;
+    @utf8InCpp String overlayName;
+    @utf8InCpp String targetPackageName;
+    @utf8InCpp String targetOverlayable;
+    List<FabricatedOverlayInternalEntry> entries;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
new file mode 100644
index 0000000..6c2af27
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternalEntry {
+    @utf8InCpp String resourceName;
+    int dataType;
+    int data;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
similarity index 73%
rename from cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
index 156f1d7..35bca98 100644
--- a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
@@ -16,6 +16,9 @@
 
 package android.os;
 
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInfo;
+
 /**
  * @hide
  */
@@ -23,13 +26,18 @@
   @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
   boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
   boolean verifyIdmap(@utf8InCpp String targetApkPath,
-					  @utf8InCpp String overlayApkPath,
+                      @utf8InCpp String overlayApkPath,
+                      @utf8InCpp String overlayName,
                       int fulfilledPolicies,
                       boolean enforceOverlayable,
                       int userId);
   @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
                                           @utf8InCpp String overlayApkPath,
+                                          @utf8InCpp String overlayName,
                                           int fulfilledPolicies,
                                           boolean enforceOverlayable,
                                           int userId);
+  @nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
+  List<FabricatedOverlayInfo> getFabricatedOverlayInfos();
+  boolean deleteFabricatedOverlay(@utf8InCpp String path);
 }
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
similarity index 100%
rename from cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index d823157..be687d9 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -55,7 +55,7 @@
     std::vector<Entry> entries_;
   };
 
-  Result<Unit> ToBinaryStream(std::ostream& stream);
+  Result<Unit> ToBinaryStream(std::ostream& stream) const;
   static Result<FabricatedOverlay> FromBinaryStream(std::istream& stream);
 
  private:
@@ -81,6 +81,8 @@
   static Result<std::unique_ptr<FabricatedOverlayContainer>> FromPath(std::string path);
   static std::unique_ptr<FabricatedOverlayContainer> FromOverlay(FabricatedOverlay&& overlay);
 
+  WARN_UNUSED OverlayManifestInfo GetManifestInfo() const;
+
   // inherited from OverlayResourceContainer
   WARN_UNUSED Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
   WARN_UNUSED Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index c4e0e1f..2588688 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,7 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 #define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 
+#include <random>
 #include <string>
 
 namespace android::idmap2::utils {
@@ -26,6 +27,8 @@
 
 bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
 
+std::string RandomStringForPath(size_t length);
+
 }  // namespace android::idmap2::utils
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h
index 7a22dc6..74a6f56 100644
--- a/cmds/idmap2/include/idmap2/ResourceContainer.h
+++ b/cmds/idmap2/include/idmap2/ResourceContainer.h
@@ -46,6 +46,7 @@
 };
 
 struct OverlayManifestInfo {
+  std::string package_name;     // NOLINT(misc-non-private-member-variables-in-classes)
   std::string name;             // NOLINT(misc-non-private-member-variables-in-classes)
   std::string target_package;   // NOLINT(misc-non-private-member-variables-in-classes)
   std::string target_name;      // NOLINT(misc-non-private-member-variables-in-classes)
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 4a348ac..4f61801 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -208,7 +208,7 @@
   return (*data)->crc;
 }
 
-Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) {
+Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
   auto data = InitializeData();
   if (!data) {
     return data.GetError();
@@ -247,19 +247,24 @@
       new FabricatedOverlayContainer(std::move(overlay), {} /* path */));
 }
 
-Result<OverlayManifestInfo> FabContainer::FindOverlayInfo(const std::string& name) const {
+OverlayManifestInfo FabContainer::GetManifestInfo() const {
   const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
-  if (name != overlay_pb.name()) {
-    return Error("Failed to find name '%s' in fabricated overlay", name.c_str());
-  }
-
   return OverlayManifestInfo{
+      .package_name = overlay_pb.package_name(),
       .name = overlay_pb.name(),
       .target_package = overlay_pb.target_package_name(),
       .target_name = overlay_pb.target_overlayable(),
   };
 }
 
+Result<OverlayManifestInfo> FabContainer::FindOverlayInfo(const std::string& name) const {
+  const OverlayManifestInfo info = GetManifestInfo();
+  if (name != info.name) {
+    return Error("Failed to find name '%s' in fabricated overlay", name.c_str());
+  }
+  return info;
+}
+
 Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info) const {
   const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
   if (info.name != overlay_pb.name()) {
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 3af1f70..98a4cea 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -47,4 +47,19 @@
 }
 #endif
 
+std::string RandomStringForPath(const size_t length) {
+  constexpr char kChars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  constexpr size_t kCharLastIndex = sizeof(kChars) - 1;
+
+  std::string out_rand;
+  out_rand.reserve(length);
+
+  std::random_device rd;
+  std::uniform_int_distribution<int> dist(0, kCharLastIndex);
+  for (size_t i = 0; i < length; i++) {
+    out_rand[i] = kChars[dist(rd) % (kCharLastIndex)];
+  }
+  return out_rand;
+}
+
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 91cb43c..9147cca 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -118,15 +118,23 @@
     return Error("root element tag is not <manifest> in AndroidManifest.xml");
   }
 
+  std::string package_name;
+  if (auto result_str = manifest_it->GetAttributeStringValue("package")) {
+    package_name = *result_str;
+  } else {
+    return result_str.GetError();
+  }
+
   for (auto&& it : manifest_it) {
     if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
       continue;
     }
 
     OverlayManifestInfo info{};
+    info.package_name = package_name;
     if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
       if (*result_str != name) {
-        // A value for android:name was found, but either the name does not match the requested
+        // A value for android:name was found, but either a the name does not match the requested
         // name, or an <overlay> tag with no name was requested.
         continue;
       }
diff --git a/core/java/android/content/om/CriticalOverlayInfo.java b/core/java/android/content/om/CriticalOverlayInfo.java
index 8d14f43..8fbc698 100644
--- a/core/java/android/content/om/CriticalOverlayInfo.java
+++ b/core/java/android/content/om/CriticalOverlayInfo.java
@@ -57,4 +57,9 @@
      */
     @NonNull
     OverlayIdentifier getOverlayIdentifier();
+
+    /**
+     * Returns whether or not the overlay is a {@link FabricatedOverlay}.
+     */
+    boolean isFabricated();
 }
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
new file mode 100644
index 0000000..d62b47b
--- /dev/null
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInternalEntry;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
+ *
+ * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
+ * overlayable policies a fabricated overlay fulfills are the same policies the creator of the
+ * overlay fulfill. For example, a fabricated overlay created by a platform signed package on the
+ * system partition would fulfil the {@code system} and {@code signature} policies.
+ *
+ * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
+ * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are wiped.
+ *
+ * Processes with {@link Android.Manifest.permission.CHANGE_OVERLAY_PACKAGES} can manage normal
+ * overlays and fabricated overlays.
+ * @hide
+ */
+public class FabricatedOverlay {
+
+    /** Retrieves the identifier for this fabricated overlay. */
+    public OverlayIdentifier getIdentifier() {
+        return new OverlayIdentifier(
+                mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
+    }
+
+    public static class Builder {
+        private final String mOwningPackage;
+        private final String mName;
+        private final String mTargetPackage;
+        private String mTargetOverlayable = "";
+        private final ArrayList<FabricatedOverlayInternalEntry> mEntries = new ArrayList<>();
+
+        /**
+         * Constructs a build for a fabricated overlay.
+         *
+         * @param owningPackage the name of the package that owns the fabricated overlay (must
+         *                      be a package name of this UID).
+         * @param name a name used to uniquely identify the fabricated overlay owned by
+         *             {@param owningPackageName}
+         * @param targetPackage the name of the package to overlay
+         */
+        public Builder(@NonNull String owningPackage, @NonNull String name,
+                @NonNull String targetPackage) {
+            Preconditions.checkStringNotEmpty(owningPackage,
+                    "'owningPackage' must not be empty nor null");
+            Preconditions.checkStringNotEmpty(name,
+                    "'name'' must not be empty nor null");
+            Preconditions.checkStringNotEmpty(targetPackage,
+                    "'targetPackage' must not be empty nor null");
+
+            mOwningPackage = owningPackage;
+            mName = name;
+            mTargetPackage = targetPackage;
+        }
+
+        /**
+         * Sets the name of the overlayable resources to overlay (can be null).
+         */
+        public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
+            mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
+            return this;
+        }
+
+        /**
+         * Sets the value of
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *                     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the unsigned 32 bit integer representing the new value
+         *
+         * @see android.util.TypedValue#type
+         */
+        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+            entry.resourceName = resourceName;
+            entry.dataType = dataType;
+            entry.data = value;
+            mEntries.add(entry);
+            return this;
+        }
+
+        /** Builds an immutable fabricated overlay. */
+        public FabricatedOverlay build() {
+            final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
+            overlay.packageName = mOwningPackage;
+            overlay.overlayName = mName;
+            overlay.targetPackageName = mTargetPackage;
+            overlay.targetOverlayable = mTargetOverlayable;
+            overlay.entries = new ArrayList<>();
+            overlay.entries.addAll(mEntries);
+            return new FabricatedOverlay(overlay);
+        }
+    }
+
+    final FabricatedOverlayInternal mOverlay;
+    private FabricatedOverlay(FabricatedOverlayInternal overlay) {
+        mOverlay = overlay;
+    }
+}
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index a99e792..e319d2c 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -175,7 +175,7 @@
      * Invalidates and removes the idmap for an overlay,
      * @param packageName The name of the overlay package whose idmap should be deleted.
      */
-    void invalidateCachesForOverlay(in String packageName, in int userIs);
+    void invalidateCachesForOverlay(in String packageName, in int userId);
 
     /**
      * Perform a series of requests related to overlay packages. This is an
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 8c316d7..c66f49c 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -214,6 +214,12 @@
     private OverlayIdentifier mIdentifierCached;
 
     /**
+     *
+     * @hide
+     */
+    public final boolean isFabricated;
+
+    /**
      * Create a new OverlayInfo based on source with an updated state.
      *
      * @param source the source OverlayInfo to base the new instance on
@@ -224,7 +230,7 @@
     public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
         this(source.packageName, source.overlayName, source.targetPackageName,
                 source.targetOverlayableName, source.category, source.baseCodePath, state,
-                source.userId, source.priority, source.isMutable);
+                source.userId, source.priority, source.isMutable, source.isFabricated);
     }
 
     /** @hide */
@@ -233,14 +239,15 @@
             @Nullable String targetOverlayableName, @Nullable String category,
             @NonNull String baseCodePath, int state, int userId, int priority, boolean isMutable) {
         this(packageName, null /* overlayName */, targetPackageName, targetOverlayableName,
-                category, baseCodePath, state, userId, priority, isMutable);
+                category, baseCodePath, state, userId, priority, isMutable,
+                false /* isFabricated */);
     }
 
     /** @hide */
     public OverlayInfo(@NonNull String packageName, @Nullable String overlayName,
             @NonNull String targetPackageName, @Nullable String targetOverlayableName,
             @Nullable String category, @NonNull String baseCodePath, int state, int userId,
-            int priority, boolean isMutable) {
+            int priority, boolean isMutable, boolean isFabricated) {
         this.packageName = packageName;
         this.overlayName = overlayName;
         this.targetPackageName = targetPackageName;
@@ -251,6 +258,7 @@
         this.userId = userId;
         this.priority = priority;
         this.isMutable = isMutable;
+        this.isFabricated = isFabricated;
         ensureValidState();
     }
 
@@ -266,6 +274,7 @@
         userId = source.readInt();
         priority = source.readInt();
         isMutable = source.readBoolean();
+        isFabricated = source.readBoolean();
         ensureValidState();
     }
 
@@ -339,6 +348,24 @@
      * @hide
      */
     @Override
+    public boolean isFabricated() {
+        return isFabricated;
+    }
+
+    /**
+     * Full path to the base APK or fabricated overlay for this overlay package.
+     *
+     * @hide
+     */
+    public String getBaseCodePath() {
+        return baseCodePath;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
     @NonNull
     public OverlayIdentifier getOverlayIdentifier() {
         if (mIdentifierCached == null) {
@@ -390,6 +417,7 @@
         dest.writeInt(userId);
         dest.writeInt(priority);
         dest.writeBoolean(isMutable);
+        dest.writeBoolean(isFabricated);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index d03ad9d..73be0ff 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,6 +20,9 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -29,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Container for a batch of requests to the OverlayManagerService.
@@ -65,7 +69,8 @@
             final int request = source.readInt();
             final OverlayIdentifier overlay = source.readParcelable(null);
             final int userId = source.readInt();
-            mRequests.add(new Request(request, overlay, userId));
+            final Bundle extras = source.readBundle(null);
+            mRequests.add(new Request(request, overlay, userId, extras));
         }
     }
 
@@ -95,21 +100,35 @@
 
         public static final int TYPE_SET_ENABLED = 0;
         public static final int TYPE_SET_DISABLED = 1;
+        public static final int TYPE_REGISTER_FABRICATED = 2;
+        public static final int TYPE_UNREGISTER_FABRICATED = 3;
 
-        @RequestType public final int type;
+        public static final String BUNDLE_FABRICATED_OVERLAY = "fabricated_overlay";
+
+        @RequestType
+        public final int type;
+        @NonNull
         public final OverlayIdentifier overlay;
         public final int userId;
+        @Nullable
+        public final Bundle extras;
 
         public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
                 final int userId) {
+            this(type, overlay, userId, null /* extras */);
+        }
+
+        public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
+                final int userId, @Nullable Bundle extras) {
             this.type = type;
             this.overlay = overlay;
             this.userId = userId;
+            this.extras = extras;
         }
 
         @Override
         public String toString() {
-            return String.format("Request{type=0x%02x (%s), overlay=%s, userId=%d}",
+            return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}",
                     type, typeToString(), overlay, userId);
         }
 
@@ -123,6 +142,8 @@
             switch (type) {
                 case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
                 case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
+                case TYPE_REGISTER_FABRICATED: return "TYPE_REGISTER_FABRICATED";
+                case TYPE_UNREGISTER_FABRICATED: return "TYPE_UNREGISTER_FABRICATED";
                 default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
             }
         }
@@ -172,6 +193,40 @@
         }
 
         /**
+         * Registers the fabricated overlay with the overlay manager so it can be enabled and
+         * disabled for any user.
+         *
+         * The fabricated overlay is initialized in a disabled state. If an overlay is re-registered
+         * the existing overlay will be replaced by the newly registered overlay and the enabled
+         * state of the overlay will be left unchanged if the target package and target overlayable
+         * have not changed.
+         *
+         * @param overlay the overlay to register with the overlay manager
+         *
+         * @hide
+         */
+        public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+            final Bundle extras = new Bundle();
+            extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
+            mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
+                    UserHandle.USER_ALL, extras));
+            return this;
+        }
+
+        /**
+         * Disables and removes the overlay from the overlay manager for all users.
+         *
+         * @param overlay the overlay to disable and remove
+         *
+         * @hide
+         */
+        public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+            mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
+                    UserHandle.USER_ALL));
+            return this;
+        }
+
+        /**
          * Create a new transaction out of the requests added so far. Execute
          * the transaction by calling OverlayManager#commit.
          *
@@ -197,6 +252,7 @@
             dest.writeInt(req.type);
             dest.writeParcelable(req.overlay, flags);
             dest.writeInt(req.userId);
+            dest.writeBundle(req.extras);
         }
     }
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 66bdb9b..b7aa30f 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2807,7 +2807,7 @@
      *                        limits length of the name to the {@link #MAX_FILE_NAME_SIZE}.
      * @return Success if it's valid.
      */
-    public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+    public static String validateName(String name, boolean requireSeparator,
             boolean requireFilename) {
         final int N = name.length();
         boolean hasSep = false;
@@ -2828,18 +2828,28 @@
                 front = true;
                 continue;
             }
-            return input.error("bad character '" + c + "'");
+            return "bad character '" + c + "'";
         }
         if (requireFilename) {
             if (!FileUtils.isValidExtFilename(name)) {
-                return input.error("Invalid filename");
+                return "Invalid filename";
             } else if (N > MAX_FILE_NAME_SIZE) {
-                return input.error("the length of the name is greater than " + MAX_FILE_NAME_SIZE);
+                return "the length of the name is greater than " + MAX_FILE_NAME_SIZE;
             }
         }
-        return hasSep || !requireSeparator
-                ? input.success(null)
-                : input.error("must have at least one '.' separator");
+        return hasSep || !requireSeparator ? null : "must have at least one '.' separator";
+    }
+
+    /**
+     * @see #validateName(String, boolean, boolean)
+     */
+    public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+            boolean requireFilename) {
+        final String errorMessage = validateName(name, requireSeparator, requireFilename);
+        if (errorMessage != null) {
+            return input.error(errorMessage);
+        }
+        return input.success(null);
     }
 
     /**
diff --git a/core/tests/overlaytests/device/res/values/config.xml b/core/tests/overlaytests/device/res/values/config.xml
index e918268..a30d66f 100644
--- a/core/tests/overlaytests/device/res/values/config.xml
+++ b/core/tests/overlaytests/device/res/values/config.xml
@@ -2,6 +2,7 @@
 <resources>
     <string name="str">none</string>
     <string name="str2">none</string>
+    <integer name="overlaid">0</integer>
     <integer name="matrix_100000">100</integer>
     <integer name="matrix_100001">100</integer>
     <integer name="matrix_100010">100</integer>
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
new file mode 100644
index 0000000..3465989
--- /dev/null
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(JUnit4.class)
+@MediumTest
+public class FabricatedOverlaysTest {
+    private static final String TAG = "FabricatedOverlaysTest";
+    private final String TEST_RESOURCE = "integer/overlaid";
+    private final String TEST_OVERLAY_NAME = "Test";
+
+    private Context mContext;
+    private Resources mResources;
+    private OverlayManager mOverlayManager;
+    private int mUserId;
+    private UserHandle mUserHandle;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResources = mContext.getResources();
+        mOverlayManager = mContext.getSystemService(OverlayManager.class);
+        mUserId = UserHandle.myUserId();
+        mUserHandle = UserHandle.of(mUserId);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        final OverlayManagerTransaction.Builder cleanUp = new OverlayManagerTransaction.Builder();
+        mOverlayManager.getOverlayInfosForTarget(mContext.getPackageName(), mUserHandle).forEach(
+                info -> {
+                    if (info.isFabricated()) {
+                        cleanUp.unregisterFabricatedOverlay(info.getOverlayIdentifier());
+                    }
+                });
+        mOverlayManager.commit(cleanUp.build());
+    }
+
+    @Test
+    public void testFabricatedOverlay() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+
+        OverlayInfo info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertFalse(info.isEnabled());
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertTrue(info.isEnabled());
+
+        waitForResourceValue(1);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .unregisterFabricatedOverlay(overlay.getIdentifier())
+                .build());
+
+        waitForResourceValue(0);
+    }
+
+    @Test
+    public void testRegisterEnableAtomic() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        waitForResourceValue(1);
+    }
+
+    @Test
+    public void testRegisterTwice() throws Exception {
+        FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        waitForResourceValue(1);
+        overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 2)
+                .build();
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+        waitForResourceValue(2);
+    }
+
+    @Test
+    public void testInvalidOwningPackageName() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+            mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                    .registerFabricatedOverlay(overlay)
+                    .setEnabled(overlay.getIdentifier(), true, mUserId)
+                    .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testInvalidOverlayName() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), "invalid@name", mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testOverlayIdentifierLongest() throws Exception {
+        final int maxLength = 255 - 11; // 11 reserved characters
+        final String longestName = String.join("",
+                Collections.nCopies(maxLength - mContext.getPackageName().length(), "a"));
+        {
+            FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+                    longestName, mContext.getPackageName())
+                    .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                    .build();
+
+            mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                    .registerFabricatedOverlay(overlay)
+                    .build());
+            assertNotNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+        }
+        {
+            FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+                    longestName + "a", mContext.getPackageName())
+                    .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                    .build();
+
+            assertThrows(SecurityException.class, () ->
+                    mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                            .registerFabricatedOverlay(overlay)
+                            .build()));
+
+            assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+        }
+    }
+
+    @Test
+    public void testInvalidResourceValues() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testTransactionFailRollback() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .setEnabled(new OverlayIdentifier("not-valid"), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    void waitForResourceValue(final int expectedValue) throws TimeoutException {
+        final long timeOutDuration = 10000;
+        final long endTime = System.currentTimeMillis() + timeOutDuration;
+        final String resourceName = TEST_RESOURCE;
+        final int resourceId = mResources.getIdentifier(resourceName, "",
+                mContext.getPackageName());
+        int resourceValue = 0;
+        while (System.currentTimeMillis() < endTime) {
+            resourceValue = mResources.getInteger(resourceId);
+            if (resourceValue == expectedValue) {
+                return;
+            }
+        }
+        final String paths = TextUtils.join(",", mResources.getAssets().getApkPaths());
+        Log.w(TAG, "current paths: [" + paths + "]", new Throwable());
+        throw new TimeoutException("Timed out waiting for '" + resourceName + "' value to equal '"
+                + expectedValue + "': current value is '" + resourceValue + "'");
+    }
+}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
index 594fa47..27d7342 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
@@ -132,6 +132,6 @@
             int userId) {
         final OverlayInfo oi = mOverlayManager.getOverlayInfo(overlay, UserHandle.of(userId));
         assertNotNull(oi);
-        assertEquals(oi.isEnabled(), enabled);
+        assertEquals(enabled, oi.isEnabled());
     }
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 96cfe02..a0b431e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -82,6 +82,7 @@
         ":framework_native_aidl",
         ":gsiservice_aidl",
         ":idmap2_aidl",
+        ":idmap2_core_aidl",
         ":inputconstants_aidl",
         ":installd_aidl",
         ":storaged_aidl",
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 19fa920..2ebc8ed 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -20,17 +20,23 @@
 
 import static com.android.server.om.OverlayManagerService.TAG;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.os.IBinder;
 import android.os.IIdmap2;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemService;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.server.FgThread;
 
 import java.io.File;
+import java.util.List;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -104,10 +110,12 @@
         return sInstance;
     }
 
-    String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-            int userId) throws TimeoutException, RemoteException {
+    String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+            @Nullable String overlayName, int policies, boolean enforce, int userId)
+            throws TimeoutException, RemoteException {
         try (Connection c = connect()) {
-            return mService.createIdmap(targetPath, overlayPath, policies, enforce, userId);
+            return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+                    policies, enforce, userId);
         }
     }
 
@@ -117,11 +125,12 @@
         }
     }
 
-    boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-             int userId)
+    boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+            @Nullable String overlayName, int policies, boolean enforce, int userId)
             throws Exception {
         try (Connection c = connect()) {
-            return mService.verifyIdmap(targetPath, overlayPath, policies, enforce, userId);
+            return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+                    policies, enforce, userId);
         }
     }
 
@@ -129,12 +138,38 @@
         try (Connection c = connect()) {
             return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
         } catch (Exception e) {
-            Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
-                    + e.getMessage());
+            Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
             return false;
         }
     }
 
+    FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+        try (Connection c = connect()) {
+            return mService.createFabricatedOverlay(overlay);
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
+            return null;
+        }
+    }
+
+    boolean deleteFabricatedOverlay(@NonNull String path) {
+        try (Connection c = connect()) {
+            return mService.deleteFabricatedOverlay(path);
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
+            return false;
+        }
+    }
+
+    List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        try (Connection c = connect()) {
+            return mService.getFabricatedOverlayInfos();
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to get fabricated overlays", e);
+            return null;
+        }
+    }
+
     private IBinder getIdmapService() throws TimeoutException, RemoteException {
         SystemService.start(IDMAP_DAEMON);
 
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index e87bf55..64362c9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -22,7 +22,10 @@
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
+import android.content.pm.PackageParser;
 import android.os.Build.VERSION_CODES;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.os.OverlayablePolicy;
 import android.os.SystemProperties;
 import android.text.TextUtils;
@@ -31,6 +34,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.IOException;
+import java.util.List;
 
 /**
  * Handle the creation and deletion of idmap files.
@@ -75,24 +79,25 @@
      * modified.
      */
     boolean createIdmap(@NonNull final AndroidPackage targetPackage,
-            @NonNull final AndroidPackage overlayPackage, int userId) {
+            @NonNull final AndroidPackage overlayPackage, String overlayBasePath,
+            String overlayName, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "create idmap for " + targetPackage.getPackageName() + " and "
                     + overlayPackage.getPackageName());
         }
         final String targetPath = targetPackage.getBaseApkPath();
-        final String overlayPath = overlayPackage.getBaseApkPath();
         try {
             int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
             boolean enforce = enforceOverlayable(overlayPackage);
-            if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
+            if (mIdmapDaemon.verifyIdmap(targetPath, overlayBasePath, overlayName, policies,
+                    enforce, userId)) {
                 return false;
             }
-            return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
+            return mIdmapDaemon.createIdmap(targetPath, overlayBasePath, overlayName, policies,
                     enforce, userId) != null;
         } catch (Exception e) {
             Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
-                    + overlayPath + ": " + e.getMessage());
+                    + overlayBasePath, e);
             return false;
         }
     }
@@ -104,7 +109,7 @@
         try {
             return mIdmapDaemon.removeIdmap(oi.baseCodePath, userId);
         } catch (Exception e) {
-            Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
+            Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath, e);
             return false;
         }
     }
@@ -114,6 +119,29 @@
     }
 
     /**
+     * @return the list of all fabricated overlays
+     */
+    List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        return mIdmapDaemon.getFabricatedOverlayInfos();
+    }
+
+    /**
+     * Creates a fabricated overlay and persists it to disk.
+     * @return the path to the fabricated overlay
+     */
+    FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+        return mIdmapDaemon.createFabricatedOverlay(overlay);
+    }
+
+    /**
+     * Deletes the fabricated overlay file on disk.
+     * @return whether the path was deleted
+     */
+    boolean deleteFabricatedOverlay(@NonNull String path) {
+        return mIdmapDaemon.deleteFabricatedOverlay(path);
+    }
+
+    /**
      * Checks if overlayable and policies should be enforced on the specified overlay for backwards
      * compatibility with pre-Q overlays.
      */
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b0e2922..905733c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -24,8 +24,10 @@
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_REASON;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
@@ -55,8 +57,10 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.FabricatedOverlayInternal;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -71,6 +75,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -97,6 +102,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
@@ -618,8 +624,7 @@
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setEnabled(overlay, enable, realUserId)
-                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
+                            updateTargetPackages(mImpl.setEnabled(overlay, enable, realUserId));
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -754,8 +759,7 @@
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setHighestPriority(overlay, realUserId)
-                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
+                            updateTargetPackages(mImpl.setHighestPriority(overlay, realUserId));
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -867,25 +871,63 @@
             }
         }
 
-        private Optional<PackageAndUser> executeRequest(
-                @NonNull final OverlayManagerTransaction.Request request) throws Exception {
-            final int realUserId = handleIncomingUser(request.userId, request.typeToString());
-            enforceActor(request.overlay, request.typeToString(), realUserId);
+        private Set<PackageAndUser> executeRequest(
+                @NonNull final OverlayManagerTransaction.Request request)
+                throws OperationFailedException {
+            Objects.requireNonNull(request, "Transaction contains a null request");
+            Objects.requireNonNull(request.overlay,
+                    "Transaction overlay identifier must be non-null");
+
+            final int callingUid = Binder.getCallingUid();
+            final int realUserId;
+            if (request.type == TYPE_REGISTER_FABRICATED
+                    || request.type == TYPE_UNREGISTER_FABRICATED) {
+                if (request.userId != UserHandle.USER_ALL) {
+                    throw new IllegalArgumentException(request.typeToString()
+                            + " unsupported for user " + request.userId);
+                }
+                realUserId = UserHandle.USER_ALL;
+
+                // Enforce that the calling process can only register and unregister fabricated
+                // overlays using its package name.
+                final String pkgName = request.overlay.getPackageName();
+                if (callingUid != Process.ROOT_UID && !ArrayUtils.contains(
+                        mPackageManager.getPackagesForUid(callingUid), pkgName)) {
+                    throw new IllegalArgumentException("UID " + callingUid + " does own package"
+                            + "name " + pkgName);
+                }
+            } else {
+                // Enforce actor requirements for enabling, disabling, and reordering overlays.
+                realUserId = handleIncomingUser(request.userId, request.typeToString());
+                enforceActor(request.overlay, request.typeToString(), realUserId);
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
                 switch (request.type) {
                     case TYPE_SET_ENABLED:
-                        Optional<PackageAndUser> opt1 =
-                                mImpl.setEnabled(request.overlay, true, realUserId);
-                        Optional<PackageAndUser> opt2 =
-                                mImpl.setHighestPriority(request.overlay, realUserId);
-                        // Both setEnabled and setHighestPriority affected the same
-                        // target package and user: if both return non-empty
-                        // Optionals, they are identical
-                        return opt1.isPresent() ? opt1 : opt2;
+                        Set<PackageAndUser> result = null;
+                        result = CollectionUtils.addAll(result,
+                                mImpl.setEnabled(request.overlay, true, realUserId));
+                        result = CollectionUtils.addAll(result,
+                                mImpl.setHighestPriority(request.overlay, realUserId));
+                        return CollectionUtils.emptyIfNull(result);
+
                     case TYPE_SET_DISABLED:
                         return mImpl.setEnabled(request.overlay, false, realUserId);
+
+                    case TYPE_REGISTER_FABRICATED:
+                        final FabricatedOverlayInternal fabricated =
+                                request.extras.getParcelable(
+                                        OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY
+                                );
+                        Objects.requireNonNull(fabricated,
+                                "no fabricated overlay attached to request");
+                        return mImpl.registerFabricatedOverlay(fabricated);
+
+                    case TYPE_UNREGISTER_FABRICATED:
+                        return mImpl.unregisterFabricatedOverlay(request.overlay);
+
                     default:
                         throw new IllegalArgumentException("unsupported request: " + request);
                 }
@@ -895,7 +937,7 @@
         }
 
         private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
-                throws Exception {
+                throws OperationFailedException {
             if (DEBUG) {
                 Slog.d(TAG, "commit " + transaction);
             }
@@ -905,25 +947,25 @@
 
             // map: userId -> set<package-name>: target packages of overlays in
             // this transaction
-            SparseArray<Set<String>> transactionTargets = new SparseArray<>();
+            final SparseArray<Set<String>> transactionTargets = new SparseArray<>();
 
             // map: userId -> set<package-name>: packages that need to reload
             // their resources due to changes to the overlays in this
             // transaction
-            SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
+            final SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
 
             synchronized (mLock) {
-
                 // execute the requests (as calling user)
                 for (final OverlayManagerTransaction.Request request : transaction) {
-                    executeRequest(request).ifPresent(target -> {
-                        Set<String> userTargets = transactionTargets.get(target.userId);
-                        if (userTargets == null) {
-                            userTargets = new ArraySet<>();
-                            transactionTargets.put(target.userId, userTargets);
-                        }
-                        userTargets.add(target.packageName);
-                    });
+                    executeRequest(request).forEach(
+                            target -> {
+                                Set<String> userTargets = transactionTargets.get(target.userId);
+                                if (userTargets == null) {
+                                    userTargets = new ArraySet<>();
+                                    transactionTargets.put(target.userId, userTargets);
+                                }
+                                userTargets.add(target.packageName);
+                            });
                 }
 
                 // past the point of no return: the entire transaction has been
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 6eb4edc..fb183f5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -32,6 +32,9 @@
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -42,6 +45,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -97,6 +101,9 @@
         if (!Objects.equals(theTruth.getOverlayTargetName(), oldSettings.targetOverlayableName)) {
             return true;
         }
+        if (oldSettings.isFabricated) {
+            return true;
+        }
         boolean isMutable = isPackageConfiguredMutable(theTruth);
         if (isMutable != oldSettings.isMutable) {
             return true;
@@ -108,6 +115,20 @@
         return false;
     }
 
+    private boolean mustReinitializeOverlay(@NonNull final FabricatedOverlayInfo theTruth,
+            @Nullable final OverlayInfo oldSettings) {
+        if (oldSettings == null) {
+            return true;
+        }
+        if (!Objects.equals(theTruth.targetPackageName, oldSettings.targetPackageName)) {
+            return true;
+        }
+        if (!Objects.equals(theTruth.targetOverlayable, oldSettings.targetOverlayableName)) {
+            return true;
+        }
+        return false;
+    }
+
     OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager,
             @NonNull final IdmapManager idmapManager,
             @NonNull final OverlayManagerSettings settings,
@@ -156,6 +177,18 @@
             }
         }
 
+        // Update the state of all fabricated overlays, and initialize fabricated overlays in the
+        // new user.
+        for (final FabricatedOverlayInfo info : getFabricatedOverlayInfos()) {
+            try {
+                CollectionUtils.addAll(updatedTargets, registerFabricatedOverlay(
+                        info, newUserId));
+            } catch (OperationFailedException e) {
+                Slog.e(TAG, "failed to initialize fabricated overlay of '" + info.path
+                        + "' for user " + newUserId + "", e);
+            }
+        }
+
         // Collect all of the categories in which we have at least one overlay enabled.
         final ArraySet<String> enabledCategories = new ArraySet<>();
         final ArrayMap<String, List<OverlayInfo>> userOverlays =
@@ -197,6 +230,7 @@
             }
         }
 
+        cleanStaleResourceCache();
         return updatedTargets;
     }
 
@@ -306,7 +340,8 @@
                         pkg.getOverlayTargetName(), pkg.getBaseApkPath(),
                         isPackageConfiguredMutable(pkg),
                         isPackageConfiguredEnabled(pkg),
-                        getPackageConfiguredPriority(pkg), pkg.getOverlayCategory());
+                        getPackageConfiguredPriority(pkg), pkg.getOverlayCategory(),
+                        false);
             } else if (priority != currentInfo.priority) {
                 // Changing the priority of an overlay does not cause its settings to be
                 // reinitialized. Reorder the overlay and update its target package.
@@ -367,7 +402,8 @@
         return mSettings.getOverlaysForUser(userId);
     }
 
-    Optional<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
+    @NonNull
+    Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
             final boolean enable, final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d",
@@ -386,9 +422,9 @@
             modified |= updateState(oi, userId, 0);
 
             if (modified) {
-                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
+                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
             }
-            return Optional.empty();
+            return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
         }
@@ -445,6 +481,116 @@
         }
     }
 
+    @NonNull
+    Set<PackageAndUser> registerFabricatedOverlay(
+            @NonNull final FabricatedOverlayInternal overlay)
+            throws OperationFailedException {
+        if (ParsingPackageUtils.validateName(overlay.overlayName,
+                false /* requireSeparator */, true /* requireFilename */) != null) {
+            throw new OperationFailedException(
+                    "overlay name can only consist of alphanumeric characters, '_', and '.'");
+        }
+
+        final FabricatedOverlayInfo info = mIdmapManager.createFabricatedOverlay(overlay);
+        if (info == null) {
+            throw new OperationFailedException("failed to create fabricated overlay");
+        }
+
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        for (int userId : mSettings.getUsers()) {
+            updatedTargets.addAll(registerFabricatedOverlay(info, userId));
+        }
+        return updatedTargets;
+    }
+
+    @NonNull
+    private Set<PackageAndUser> registerFabricatedOverlay(
+            @NonNull final FabricatedOverlayInfo info, int userId)
+            throws OperationFailedException {
+        final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
+                info.packageName, info.overlayName);
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
+        if (oi != null) {
+            if (!oi.isFabricated) {
+                throw new OperationFailedException("non-fabricated overlay with name '" +
+                        oi.overlayName + "' already present in '" + oi.packageName + "'");
+            }
+        }
+        try {
+            if (mustReinitializeOverlay(info, oi)) {
+                if (oi != null) {
+                    // If the fabricated overlay changes its target package, update the previous
+                    // target package so it no longer is overlaid.
+                    updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+                }
+                oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName,
+                        info.targetOverlayable, info.path, true, false,
+                        OverlayConfig.DEFAULT_PRIORITY, null, true);
+            } else {
+                // The only non-critical part of the info that will change is path to the fabricated
+                // overlay.
+                mSettings.setBaseCodePath(overlayIdentifier, userId, info.path);
+            }
+            if (updateState(oi, userId, 0)) {
+                updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+            }
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
+        }
+
+        return updatedTargets;
+    }
+
+    @NonNull
+    Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        for (int userId : mSettings.getUsers()) {
+            updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId));
+        }
+        return updatedTargets;
+    }
+
+    @NonNull
+    private Set<PackageAndUser> unregisterFabricatedOverlay(
+            @NonNull final OverlayIdentifier overlay, int userId) {
+        final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId);
+        if (oi != null) {
+            mSettings.remove(overlay, userId);
+            if (oi.isEnabled()) {
+                // Removing a fabricated overlay only changes the overlay path of a package if it is
+                // currently enabled.
+                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+            }
+        }
+        return Set.of();
+    }
+
+
+    private void cleanStaleResourceCache() {
+        // Clean up fabricated overlays that are no longer registered in any user.
+        final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+        for (final FabricatedOverlayInfo info : mIdmapManager.getFabricatedOverlayInfos()) {
+            if (!fabricatedPaths.contains(info.path)) {
+                mIdmapManager.deleteFabricatedOverlay(info.path);
+            }
+        }
+    }
+
+    /**
+     * Retrieves information about the fabricated overlays still in use.
+     * @return
+     */
+    @NonNull
+    private List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+        // Filter out stale fabricated overlays.
+        final ArrayList<FabricatedOverlayInfo> infos = new ArrayList<>(
+                mIdmapManager.getFabricatedOverlayInfos());
+        infos.removeIf(info -> !fabricatedPaths.contains(info.path));
+        return infos;
+    }
+
     private boolean isPackageConfiguredMutable(@NonNull final AndroidPackage overlay) {
         // TODO(162841629): Support overlay name in OverlayConfig
         return mOverlayConfig.isMutable(overlay.getPackageName());
@@ -485,7 +631,7 @@
         }
     }
 
-    Optional<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
+    Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
             final int userId) throws OperationFailedException {
         try{
             if (DEBUG) {
@@ -500,9 +646,9 @@
             }
 
             if (mSettings.setHighestPriority(overlay, userId)) {
-                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+                return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
             }
-            return Optional.empty();
+            return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
         }
@@ -563,7 +709,11 @@
             if (!oi.isEnabled()) {
                 continue;
             }
-            paths.addApkPath(oi.baseCodePath);
+            if (oi.isFabricated()) {
+                paths.addNonApkPath(oi.baseCodePath);
+            } else {
+                paths.addApkPath(oi.baseCodePath);
+            }
         }
         return paths.build();
     }
@@ -585,18 +735,21 @@
             return mSettings.remove(overlay, userId);
         }
 
-        // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native
-        // layers.
-        if (targetPackage != null && !("android".equals(info.getTargetPackageName())
-                && !isPackageConfiguredMutable(overlayPackage))) {
-            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
+        modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory());
+        if (!info.isFabricated()) {
+            modified |= mSettings.setBaseCodePath(overlay, userId, overlayPackage.getBaseApkPath());
         }
 
-        modified |= mSettings.setBaseCodePath(overlay, userId, overlayPackage.getBaseApkPath());
-        modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory());
+        // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native
+        // layers.
+        final OverlayInfo updatedOverlayInfo = mSettings.getOverlayInfo(overlay, userId);
+        if (targetPackage != null && !("android".equals(info.getTargetPackageName())
+                && !isPackageConfiguredMutable(overlayPackage))) {
+            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage,
+                    updatedOverlayInfo.baseCodePath, overlay.getOverlayName(), userId);
+        }
 
         final @OverlayInfo.State int currentState = mSettings.getState(overlay, userId);
-        final OverlayInfo updatedOverlayInfo = mSettings.getOverlayInfo(overlay, userId);
         final @OverlayInfo.State int newState = calculateNewState(updatedOverlayInfo, targetPackage,
                 userId, flags);
         if (currentState != newState) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index b46f368..e3e0906 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -25,6 +25,7 @@
 import android.content.om.OverlayInfo;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
@@ -44,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -71,11 +73,11 @@
     OverlayInfo init(@NonNull final OverlayIdentifier overlay, final int userId,
             @NonNull final String targetPackageName, @Nullable final String targetOverlayableName,
             @NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority,
-            @Nullable String overlayCategory) {
+            @Nullable String overlayCategory, boolean isFabricated) {
         remove(overlay, userId);
         final SettingsItem item = new SettingsItem(overlay, userId, targetPackageName,
                 targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled,
-                isMutable, priority, overlayCategory);
+                isMutable, priority, overlayCategory, isFabricated);
         insert(item);
         return item.getOverlayInfo();
     }
@@ -197,11 +199,10 @@
         return targetInfos;
     }
 
-    @NonNull List<OverlayInfo> getOverlayInfosForPackage(@NonNull final String packageName,
-            final int userId) {
-        final List<SettingsItem> items = selectWhereOverlay(packageName, userId);
-        items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
-        return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
+    Set<String> getAllBaseCodePaths() {
+        final Set<String> paths = new ArraySet<>();
+        mItems.forEach(item -> paths.add(item.mBaseCodePath));
+        return paths;
     }
 
     @NonNull
@@ -381,6 +382,7 @@
         pw.println("mIsMutable.............: " + item.isMutable());
         pw.println("mPriority..............: " + item.mPriority);
         pw.println("mCategory..............: " + item.mCategory);
+        pw.println("mIsFabricated..........: " + item.mIsFabricated);
 
         pw.decreaseIndent();
         pw.println("}");
@@ -450,6 +452,7 @@
         private static final String ATTR_CATEGORY = "category";
         private static final String ATTR_USER_ID = "userId";
         private static final String ATTR_VERSION = "version";
+        private static final String ATTR_IS_FABRICATED = "fabricated";
 
         @VisibleForTesting
         static final int CURRENT_VERSION = 4;
@@ -506,9 +509,11 @@
             final boolean isStatic = parser.getAttributeBoolean(null, ATTR_IS_STATIC, false);
             final int priority = parser.getAttributeInt(null, ATTR_PRIORITY);
             final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
+            final boolean isFabricated = parser.getAttributeBoolean(null, ATTR_IS_FABRICATED,
+                    false);
 
             return new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName,
-                    baseCodePath, state, isEnabled, !isStatic, priority, category);
+                    baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated);
         }
 
         public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -543,6 +548,7 @@
             XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable);
             xml.attributeInt(null, ATTR_PRIORITY, item.mPriority);
             XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
+            XmlUtils.writeBooleanAttribute(xml, ATTR_IS_FABRICATED, item.mIsFabricated);
             xml.endTag(null, TAG_ITEM);
         }
     }
@@ -559,12 +565,14 @@
         private boolean mIsMutable;
         private int mPriority;
         private String mCategory;
+        private boolean mIsFabricated;
 
         SettingsItem(@NonNull final OverlayIdentifier overlay, final int userId,
                 @NonNull final String targetPackageName,
                 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
                 final @OverlayInfo.State int state, final boolean isEnabled,
-                final boolean isMutable, final int priority,  @Nullable String category) {
+                final boolean isMutable, final int priority,  @Nullable String category,
+                final boolean isFabricated) {
             mOverlay = overlay;
             mUserId = userId;
             mTargetPackageName = targetPackageName;
@@ -576,6 +584,7 @@
             mCache = null;
             mIsMutable = isMutable;
             mPriority = priority;
+            mIsFabricated = isFabricated;
         }
 
         private String getTargetPackageName() {
@@ -646,7 +655,7 @@
             if (mCache == null) {
                 mCache = new OverlayInfo(mOverlay.getPackageName(), mOverlay.getOverlayName(),
                         mTargetPackageName, mTargetOverlayableName, mCategory, mBaseCodePath,
-                        mState, mUserId, mPriority, mIsMutable);
+                        mState, mUserId, mPriority, mIsMutable, mIsFabricated);
             }
             return mCache;
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index c8a659a..b7b72d1 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.om.FabricatedOverlay;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
@@ -27,11 +28,15 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.os.Binder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.util.TypedValue;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
@@ -75,6 +80,8 @@
                     return runSetPriority();
                 case "lookup":
                     return runLookup();
+                case "fabricate":
+                    return runFabricate();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -222,6 +229,83 @@
         return 0;
     }
 
+    private int runFabricate() throws RemoteException {
+        final PrintWriter err = getErrPrintWriter();
+        if (Binder.getCallingUid() != Process.ROOT_UID) {
+            err.println("Error: must be root to fabricate overlays through the shell");
+            return 1;
+        }
+
+        int userId = UserHandle.USER_SYSTEM;
+        String targetPackage = "";
+        String targetOverlayable = "";
+        String name = "";
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--target":
+                    targetPackage = getNextArgRequired();
+                    break;
+                case "--target-name":
+                    targetOverlayable = getNextArgRequired();
+                    break;
+                case "--name":
+                    name = getNextArgRequired();
+                    break;
+                default:
+                    err.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        if (name.isEmpty()) {
+            err.println("Error: Missing required arg '--name'");
+            return 1;
+        }
+
+        if (targetPackage.isEmpty()) {
+            err.println("Error: Missing required arg '--target'");
+            return 1;
+        }
+
+        final String resourceName = getNextArgRequired();
+        final String typeStr = getNextArgRequired();
+        final int type;
+        if (typeStr.startsWith("0x")) {
+            type = Integer.parseUnsignedInt(typeStr.substring(2), 16);
+        } else {
+            type = Integer.parseUnsignedInt(typeStr);
+        }
+        final String dataStr = getNextArgRequired();
+        final int data;
+        if (dataStr.startsWith("0x")) {
+            data = Integer.parseUnsignedInt(dataStr.substring(2), 16);
+        } else {
+            data = Integer.parseUnsignedInt(dataStr);
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            err.println("Error: failed to get package manager");
+            return 1;
+        }
+
+        final String overlayPackageName = "com.android.shell";
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                overlayPackageName, name, targetPackage)
+                .setTargetOverlayable(targetOverlayable)
+                .setResourceValue(resourceName, type, data)
+                .build();
+
+        mInterface.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+        return 0;
+    }
+
     private int runEnableExclusive() throws RemoteException {
         final PrintWriter err = getErrPrintWriter();
 
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 05ba532..38125c7 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -279,7 +279,7 @@
                     null,
                     "/path",
                     OverlayInfo.STATE_UNKNOWN, 0,
-                    0, false)
+                    0, false, false)
         }
 
         private infix fun ActorState.withCases(block: TestCase.() -> Unit) =
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index f044a09..45f82a3 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -145,7 +145,7 @@
         assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
 
         assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+                Set.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
         assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
@@ -166,7 +166,7 @@
         assertState(STATE_DISABLED, IDENTIFIER, USER);
 
         assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+                Set.of(new PackageAndUser(TARGET, USER)));
         assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         // target upgrades do not change the state of the overlay
@@ -209,10 +209,10 @@
         installPackage(target(TARGET), USER);
         installPackage(overlay(OVERLAY, TARGET), USER);
         assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+                Set.of(new PackageAndUser(TARGET, USER)));
 
         // request succeeded, but nothing changed
-        assertFalse(impl.setEnabled(IDENTIFIER, true, USER).isPresent());
+        assertTrue(impl.setEnabled(IDENTIFIER, true, USER).isEmpty());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 2aad7cc..16e0329 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -28,6 +28,8 @@
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayInfo.State;
 import android.content.om.OverlayableInfo;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -418,6 +420,9 @@
     static class FakeIdmapDaemon extends IdmapDaemon {
         private final FakeDeviceState mState;
         private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
+        private final ArrayMap<String, FabricatedOverlayInfo> mFabricatedOverlays =
+                new ArrayMap<>();
+        private int mFabricatedAssetSeq = 0;
 
         FakeIdmapDaemon(FakeDeviceState state) {
             this.mState = state;
@@ -430,10 +435,10 @@
         }
 
         @Override
-        String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-                int userId) {
+        String createIdmap(String targetPath, String overlayPath, String overlayName,
+                int policies, boolean enforce, int userId) {
             mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
-                    getCrc(overlayPath), targetPath, policies, enforce));
+                    getCrc(overlayPath), targetPath, overlayName, policies, enforce));
             return overlayPath;
         }
 
@@ -443,8 +448,8 @@
         }
 
         @Override
-        boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-                int userId) {
+        boolean verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies,
+                boolean enforce, int userId) {
             final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
             if (idmap == null) {
                 return false;
@@ -458,6 +463,29 @@
             return mIdmapFiles.containsKey(overlayPath);
         }
 
+        @Override
+        FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+            final String path = Integer.toString(mFabricatedAssetSeq++);
+            final FabricatedOverlayInfo info = new FabricatedOverlayInfo();
+            info.path = path;
+            info.overlayName = overlay.overlayName;
+            info.packageName = overlay.packageName;
+            info.targetPackageName = overlay.targetPackageName;
+            info.targetOverlayable = overlay.targetOverlayable;
+            mFabricatedOverlays.put(path, info);
+            return info;
+        }
+
+        @Override
+        boolean deleteFabricatedOverlay(@NonNull String path) {
+            return mFabricatedOverlays.remove(path) != null;
+        }
+
+        @Override
+        List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+            return new ArrayList<>(mFabricatedOverlays.values());
+        }
+
         IdmapHeader getIdmap(String overlayPath) {
             return mIdmapFiles.get(overlayPath);
         }
@@ -466,14 +494,16 @@
             private final int targetCrc;
             private final int overlayCrc;
             final String targetPath;
+            final String overlayName;
             final int policies;
             final boolean enforceOverlayable;
 
-            private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
-                    boolean enforceOverlayable) {
+            private IdmapHeader(int targetCrc, int overlayCrc, String targetPath,
+                    String overlayName, int policies, boolean enforceOverlayable) {
                 this.targetCrc = targetCrc;
                 this.overlayCrc = overlayCrc;
                 this.targetPath = targetPath;
+                this.overlayName = overlayName;
                 this.policies = policies;
                 this.enforceOverlayable = enforceOverlayable;
             }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index e89ab23..0a26f27 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -195,7 +195,8 @@
                 STATE_DISABLED,
                 0,
                 0,
-                true);
+                true,
+                false);
         insertSetting(otherTarget);
         assertFalse(mSettings.setPriority(OVERLAY_A, otherTarget.getOverlayIdentifier(), USER_0));
     }
@@ -384,7 +385,7 @@
 
     private void insertSetting(OverlayInfo oi) throws Exception {
         mSettings.init(oi.getOverlayIdentifier(), oi.userId, oi.targetPackageName, null,
-                oi.baseCodePath, true, false,0, oi.category);
+                oi.baseCodePath, true, false,0, oi.category, oi.isFabricated);
         mSettings.setState(oi.getOverlayIdentifier(), oi.userId, oi.state);
         mSettings.setEnabled(oi.getOverlayIdentifier(), oi.userId, false);
     }
@@ -425,7 +426,8 @@
                 STATE_DISABLED,
                 userId,
                 0,
-                true);
+                true,
+                false);
     }
 
     private static void assertContains(int[] haystack, int needle) {