implement drawable frros

Test: unit tests
Bug: 251283316
Change-Id: Iead53711c54596c3787eeba6dcf9ced129f94426
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 4f8faca..7a08cbd 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -222,6 +222,7 @@
     },
     data: [
         "tests/data/**/*.apk",
+        "tests/data/**/*.png",
     ],
     compile_multilib: "first",
     test_options: {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 4431164..10947dc 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -39,6 +39,7 @@
 #include "idmap2/PrettyPrintVisitor.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
+#include <fcntl.h>
 
 using android::base::StringPrintf;
 using android::binder::Status;
@@ -238,6 +239,9 @@
     if (res.dataType == Res_value::TYPE_STRING) {
       builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value(),
             res.configuration.value_or(std::string()));
+    } else if (res.binaryData.has_value()) {
+      builder.SetResourceValue(res.resourceName, res.binaryData->get(),
+            res.configuration.value_or(std::string()));
     } else {
       builder.SetResourceValue(res.resourceName, res.dataType, res.data,
             res.configuration.value_or(std::string()));
@@ -264,6 +268,7 @@
                              file_name.c_str(), kMaxFileNameLength));
     }
   } while (std::filesystem::exists(path));
+  builder.setFrroPath(path);
 
   const uid_t uid = IPCThreadState::self()->getCallingUid();
   if (!UidHasWriteAccessToPath(uid, path)) {
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index c773e11..3ad6d58 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -24,5 +24,6 @@
     int dataType;
     int data;
     @nullable @utf8InCpp String stringData;
+    @nullable ParcelFileDescriptor binaryData;
     @nullable @utf8InCpp String configuration;
 }
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 05b0618..9f57710 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -28,6 +28,7 @@
 
 #include "idmap2/ResourceContainer.h"
 #include "idmap2/Result.h"
+#include <binder/ParcelFileDescriptor.h>
 
 namespace android::idmap2 {
 
@@ -45,6 +46,15 @@
                               const std::string& data_string_value,
                               const std::string& configuration);
 
+    Builder& SetResourceValue(const std::string& resource_name,
+                              std::optional<android::base::borrowed_fd>&& binary_value,
+                              const std::string& configuration);
+
+    inline Builder& setFrroPath(std::string frro_path) {
+      frro_path_ = std::move(frro_path);
+      return *this;
+    }
+
     WARN_UNUSED Result<FabricatedOverlay> Build();
 
    private:
@@ -53,6 +63,7 @@
       DataType data_type;
       DataValue data_value;
       std::string data_string_value;
+      std::optional<android::base::borrowed_fd> data_binary_value;
       std::string configuration;
     };
 
@@ -60,6 +71,7 @@
     std::string name_;
     std::string target_package_name_;
     std::string target_overlayable_;
+    std::string frro_path_;
     std::vector<Entry> entries_;
   };
 
@@ -79,10 +91,14 @@
 
   explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                              std::string&& string_pool_data_,
+                             std::vector<android::base::borrowed_fd> binary_files_,
+                             off_t total_binary_bytes_,
                              std::optional<uint32_t> crc_from_disk = {});
 
   pb::FabricatedOverlay overlay_pb_;
   std::string string_pool_data_;
+  std::vector<android::base::borrowed_fd> binary_files_;
+  uint32_t total_binary_bytes_;
   std::optional<uint32_t> crc_from_disk_;
   mutable std::optional<SerializedData> data_;
 
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index af4dd89..2214a83 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -19,6 +19,7 @@
 
 #include <optional>
 #include <string>
+#include <android-base/unique_fd.h>
 
 #include "androidfw/AssetManager2.h"
 #include "idmap2/Result.h"
@@ -41,6 +42,7 @@
   DataType data_type;
   DataValue data_value;
   std::string data_string_value;
+  std::optional<android::base::borrowed_fd> data_binary_value;
 };
 
 struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index bde9b0b..d517e29 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -16,6 +16,10 @@
 
 #include "idmap2/FabricatedOverlay.h"
 
+#include <sys/stat.h>   // umask
+#include <sys/types.h>  // umask
+
+#include <android-base/file.h>
 #include <androidfw/ResourceUtils.h>
 #include <androidfw/StringPool.h>
 #include <google/protobuf/io/coded_stream.h>
@@ -51,9 +55,13 @@
 
 FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                                      std::string&& string_pool_data,
+                                     std::vector<android::base::borrowed_fd> binary_files,
+                                     off_t total_binary_bytes,
                                      std::optional<uint32_t> crc_from_disk)
     : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
     string_pool_data_(std::move(string_pool_data)),
+    binary_files_(std::move(binary_files)),
+    total_binary_bytes_(total_binary_bytes),
     crc_from_disk_(crc_from_disk) {
 }
 
@@ -72,14 +80,23 @@
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     const std::string& resource_name, uint8_t data_type, uint32_t data_value,
     const std::string& configuration) {
-  entries_.emplace_back(Entry{resource_name, data_type, data_value, "", configuration});
+  entries_.emplace_back(
+      Entry{resource_name, data_type, data_value, "", std::nullopt, configuration});
   return *this;
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
     const std::string& configuration) {
-  entries_.emplace_back(Entry{resource_name, data_type, 0, data_string_value, configuration});
+  entries_.emplace_back(
+      Entry{resource_name, data_type, 0, data_string_value, std::nullopt, configuration});
+  return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+    const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
+    const std::string& configuration) {
+  entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, configuration});
   return *this;
 }
 
@@ -135,7 +152,7 @@
     }
 
     value->second = TargetValue{res_entry.data_type, res_entry.data_value,
-        res_entry.data_string_value};
+        res_entry.data_string_value, res_entry.data_binary_value};
   }
 
   pb::FabricatedOverlay overlay_pb;
@@ -144,6 +161,11 @@
   overlay_pb.set_target_package_name(target_package_name_);
   overlay_pb.set_target_overlayable(target_overlayable_);
 
+  std::vector<android::base::borrowed_fd> binary_files;
+  size_t total_binary_bytes = 0;
+  // 16 for the number of bytes in the frro file before the binary data
+  const size_t FRRO_HEADER_SIZE = 16;
+
   for (auto& package : package_map) {
     auto package_pb = overlay_pb.add_packages();
     package_pb->set_name(package.first);
@@ -162,6 +184,20 @@
           if (value.second.data_type == Res_value::TYPE_STRING) {
             auto ref = string_pool.MakeRef(value.second.data_string_value);
             pb_value->set_data_value(ref.index());
+          } else if (value.second.data_binary_value.has_value()) {
+              pb_value->set_data_type(Res_value::TYPE_STRING);
+              struct stat s;
+              if (fstat(value.second.data_binary_value->get(), &s) == -1) {
+                return Error("unable to get size of binary file: %d", errno);
+              }
+              std::string uri
+                  = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
+                                 static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
+                                 static_cast<int> (s.st_size));
+              total_binary_bytes += s.st_size;
+              binary_files.emplace_back(value.second.data_binary_value->get());
+              auto ref = string_pool.MakeRef(std::move(uri));
+              pb_value->set_data_value(ref.index());
           } else {
             pb_value->set_data_value(value.second.data_value);
           }
@@ -169,10 +205,10 @@
       }
     }
   }
-
   android::BigBuffer string_buffer(kBufferSize);
   android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
-  return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
+  return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string(),
+      std::move(binary_files), total_binary_bytes);
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -190,7 +226,7 @@
     return Error("Failed to read fabricated overlay version.");
   }
 
-  if (version != 1 && version != 2) {
+  if (version < 1 || version > 3) {
     return Error("Invalid fabricated overlay version '%u'.", version);
   }
 
@@ -201,7 +237,14 @@
 
   pb::FabricatedOverlay overlay{};
   std::string sp_data;
-  if (version == 2) {
+  uint32_t total_binary_bytes;
+  if (version == 3) {
+    if (!Read32(stream, &total_binary_bytes)) {
+      return Error("Failed read total binary bytes.");
+    }
+    stream.seekg(total_binary_bytes, std::istream::cur);
+  }
+  if (version >= 2) {
     uint32_t sp_size;
     if (!Read32(stream, &sp_size)) {
       return Error("Failed read string pool size.");
@@ -211,20 +254,15 @@
       return Error("Failed to read string pool.");
     }
     sp_data = buf;
-
-    if (!overlay.ParseFromIstream(&stream)) {
-      return Error("Failed read fabricated overlay proto.");
-    }
-  } else {
-    if (!overlay.ParseFromIstream(&stream)) {
-      return Error("Failed read fabricated overlay proto.");
-    }
+  }
+  if (!overlay.ParseFromIstream(&stream)) {
+    return Error("Failed read fabricated overlay proto.");
   }
 
   // If the proto version is the latest version, then the contents of the proto must be the same
   // when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
   // proto to the latest version will likely change the contents of the fabricated overlay.
-  return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+  return FabricatedOverlay(std::move(overlay), std::move(sp_data), {}, total_binary_bytes,
                            version == kFabricatedOverlayCurrentVersion
                                                    ? std::optional<uint32_t>(crc)
                                                    : std::nullopt);
@@ -274,6 +312,14 @@
   Write32(stream, kFabricatedOverlayMagic);
   Write32(stream, kFabricatedOverlayCurrentVersion);
   Write32(stream, (*data)->pb_crc);
+  Write32(stream, total_binary_bytes_);
+  std::string file_contents;
+  for (const android::base::borrowed_fd fd : binary_files_) {
+    if (!ReadFdToString(fd, &file_contents)) {
+      return Error("Failed to read binary file data.");
+    }
+    stream.write(file_contents.data(), file_contents.length());
+  }
   Write32(stream, (*data)->sp_data.length());
   stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
   if (stream.bad()) {
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index e804c87..e13a0eb 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -17,6 +17,7 @@
 #include <android-base/file.h>
 #include <gtest/gtest.h>
 #include <idmap2/FabricatedOverlay.h>
+#include "TestHelpers.h"
 
 #include <fstream>
 #include <utility>
@@ -41,6 +42,10 @@
 }
 
 TEST(FabricatedOverlayTests, SetResourceValue) {
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
   auto overlay =
       FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
           .SetResourceValue(
@@ -54,6 +59,8 @@
               Res_value::TYPE_STRING,
               "foobar",
               "en-rUS-normal-xxhdpi-v21")
+          .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7")
+          .setFrroPath("/foo/bar/biz.frro")
           .Build();
   ASSERT_TRUE(overlay);
   auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -67,19 +74,28 @@
 
   auto pairs = container->GetOverlayData(*info);
   ASSERT_TRUE(pairs);
-  ASSERT_EQ(4U, pairs->pairs.size());
+  ASSERT_EQ(5U, pairs->pairs.size());
   auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
                                         pairs->string_pool_data->data_length, false);
 
   auto& it = pairs->pairs[0];
-  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  ASSERT_EQ("com.example.target:drawable/dr1", it.resource_name);
   auto entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(std::string("frro://foo/bar/biz.frro?offset=16&size=8341"),
+      string_pool.string8At(entry->value.data_value).value_or(""));
+  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
+  ASSERT_EQ("port-xxhdpi-v7", entry->config);
+
+  it = pairs->pairs[1];
+  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  entry = std::get_if<TargetValueWithConfig>(&it.value);
+  ASSERT_NE(nullptr, entry);
   ASSERT_EQ(1U, entry->value.data_value);
   ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
   ASSERT_EQ("port", entry->config);
 
-  it = pairs->pairs[1];
+  it = pairs->pairs[2];
   ASSERT_EQ("com.example.target:string/int3", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
@@ -87,7 +103,7 @@
   ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->value.data_type);
   ASSERT_EQ("xxhdpi-v7", entry->config);
 
-  it = pairs->pairs[2];
+  it = pairs->pairs[3];
   ASSERT_EQ("com.example.target:string/string1", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
@@ -95,7 +111,7 @@
   ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
   ASSERT_EQ("en-rUS-normal-xxhdpi-v21", entry->config);
 
-  it = pairs->pairs[3];
+  it = pairs->pairs[4];
   ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 7b7dc17..b473f26 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -260,11 +260,17 @@
   auto target = TargetResourceContainer::FromPath(target_apk_path);
   ASSERT_TRUE(target);
 
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
+                  .SetResourceValue("drawable/dr1", fd, "port-xxhdpi-v7")
+                  .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
   ASSERT_TRUE(frro);
@@ -293,14 +299,19 @@
   auto string_pool_data = data->GetStringPoolData();
   auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
 
+  std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+  uint32_t uri_index
+      = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
 
   const auto& target_inline_entries = data->GetTargetInlineEntries();
-  ASSERT_EQ(target_inline_entries.size(), 3U);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, "land-xxhdpi-v7",
+  ASSERT_EQ(target_inline_entries.size(), 4U);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::drawable::dr1, "port-xxhdpi-v7",
+                             Res_value::TYPE_STRING, uri_index);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::integer::int1, "land-xxhdpi-v7",
                              Res_value::TYPE_INT_DEC, 2U);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, "land",
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str1, "land",
                              Res_value::TYPE_REFERENCE, 0x7f010000);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, "xxhdpi-v7",
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[3], R::target::string::str2, "xxhdpi-v7",
                              Res_value::TYPE_STRING,
                              (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
 }
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index ad998b9..80c062d 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -26,24 +26,27 @@
 // clang-format off
 namespace R::target {
   namespace integer {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId int1 = 0x7f010000;
+    constexpr ResourceId int1 = 0x7f020000;
+  }
+  namespace drawable {
+    constexpr ResourceId dr1 = 0x7f010000;
   }
   namespace string {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId not_overlayable = 0x7f020003;
-    constexpr ResourceId other = 0x7f020004;
-    constexpr ResourceId policy_actor = 0x7f020005;
-    constexpr ResourceId policy_config_signature = 0x7f020006;
-    constexpr ResourceId policy_odm = 0x7f020007;
-    constexpr ResourceId policy_oem = 0x7f020008;
-    constexpr ResourceId policy_product = 0x7f020009;
-    constexpr ResourceId policy_public = 0x7f02000a;
-    constexpr ResourceId policy_signature = 0x7f02000b;
-    constexpr ResourceId policy_system = 0x7f02000c;
-    constexpr ResourceId policy_system_vendor = 0x7f02000d;
-    constexpr ResourceId str1 = 0x7f02000e;
-    constexpr ResourceId str2 = 0x7f02000f;
-    constexpr ResourceId str3 = 0x7f020010;
-    constexpr ResourceId str4 = 0x7f020011;
+    constexpr ResourceId not_overlayable = 0x7f030003;
+    constexpr ResourceId other = 0x7f030004;
+    constexpr ResourceId policy_actor = 0x7f030005;
+    constexpr ResourceId policy_config_signature = 0x7f030006;
+    constexpr ResourceId policy_odm = 0x7f030007;
+    constexpr ResourceId policy_oem = 0x7f030008;
+    constexpr ResourceId policy_product = 0x7f030009;
+    constexpr ResourceId policy_public = 0x7f03000a;
+    constexpr ResourceId policy_signature = 0x7f03000b;
+    constexpr ResourceId policy_system = 0x7f03000c;
+    constexpr ResourceId policy_system_vendor = 0x7f03000d;
+    constexpr ResourceId str1 = 0x7f03000e;
+    constexpr ResourceId str2 = 0x7f03000f;
+    constexpr ResourceId str3 = 0x7f030010;
+    constexpr ResourceId str4 = 0x7f030011;
   }  // namespace string
 }  // namespace R::target
 
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 7112eeb..68164e2 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -79,22 +79,22 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "00000000  config count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "0000000a  string pool index offset", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id: integer/int1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e  target id: string/str1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030010  target id: string/str3", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030011  target id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id: integer/int1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e  target id: string/str1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030010  target id: string/str3", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030011  target id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "000000b4  string pool size", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "........  string pool", stream.str());
 }
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 016d427..380e462 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -23,6 +23,7 @@
 #include <memory>
 #include <string>
 
+#include <fcntl.h>
 #include "R.h"
 #include "TestConstants.h"
 #include "TestHelpers.h"
@@ -76,7 +77,12 @@
   auto target_map = mapping.GetTargetToOverlayMap();
   auto entry_map = target_map.find(target_resource);
   if (entry_map == target_map.end()) {
-    return Error("Failed to find mapping for target resource");
+    std::string keys;
+    for (const auto &pair : target_map) {
+      keys.append(fmt::format("0x{:x}", pair.first)).append(" ");
+    }
+    return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+        target_resource, keys.c_str());
   }
 
   auto actual_overlay_resource = std::get_if<ResourceId>(&entry_map->second);
@@ -108,7 +114,12 @@
   auto target_map = mapping.GetTargetToOverlayMap();
   auto entry_map = target_map.find(target_resource);
   if (entry_map == target_map.end()) {
-    return Error("Failed to find mapping for target resource");
+    std::string keys;
+    for (const auto &pair : target_map) {
+      keys.append(fmt::format("{:x}", pair.first)).append(" ");
+    }
+    return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+        target_resource, keys.c_str());
   }
 
   auto config_map = std::get_if<ConfigMap>(&entry_map->second);
@@ -193,11 +204,16 @@
 }
 
 TEST(ResourceMappingTests, FabricatedOverlay) {
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
+                  .SetResourceValue("drawable/dr1", fd, "")
+                  .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
   ASSERT_TRUE(frro);
@@ -214,11 +230,16 @@
   auto string_pool_data = res.GetStringPoolData();
   auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
 
-  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+  std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+  uint32_t uri_index
+      = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
+
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
   ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
   ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
   ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
                               (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
+  ASSERT_RESULT(MappingExists(res, R::target::drawable::dr1, Res_value::TYPE_STRING, uri_index));
   ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
 }
 
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
index d5799ad..794d622 100644
--- a/cmds/idmap2/tests/TestConstants.h
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -19,8 +19,8 @@
 
 namespace android::idmap2::TestConstants {
 
-constexpr const auto TARGET_CRC = 0x7c2d4719;
-constexpr const auto TARGET_CRC_STRING = "7c2d4719";
+constexpr const auto TARGET_CRC = 0xa960a69;
+constexpr const auto TARGET_CRC_STRING = "0a960a69";
 
 constexpr const auto OVERLAY_CRC = 0xb71095cf;
 constexpr const auto OVERLAY_CRC_STRING = "b71095cf";
diff --git a/cmds/idmap2/tests/data/overlay/res/drawable/android.png b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
new file mode 100644
index 0000000..b7317b0
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build
index e6df742..cd13a7e 100755
--- a/cmds/idmap2/tests/data/target/build
+++ b/cmds/idmap2/tests/data/target/build
@@ -17,5 +17,7 @@
 rm compiled.flata
 
 aapt2 compile res/values/values.xml -o .
-aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat
-rm values_values.arsc.flat
\ No newline at end of file
+aapt2 compile res/drawable/dr1.png -o .
+aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat drawable_dr1.png.flat
+rm values_values.arsc.flat
+rm drawable_dr1.png.flat
diff --git a/cmds/idmap2/tests/data/target/res/drawable/dr1.png b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
new file mode 100644
index 0000000..1a56e68
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
index 57e6c43..aac9081 100644
--- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml
+++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
@@ -63,6 +63,7 @@
         <item type="string" name="y" />
         <item type="string" name="z" />
         <item type="integer" name="int1" />
+        <item type="drawable" name="dr1" />
     </policy>
 </overlayable>
 
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index cc3491d..680eeb6 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index 4a58c5e..145e737 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 3ca0560..dbefa65 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
+import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
 
 import com.android.internal.util.Preconditions;
@@ -169,6 +170,24 @@
             return this;
         }
 
+        /**
+         * Sets the value of the fabricated overlay
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *                     [package]:type/entry)
+         * @param value the file descriptor whose contents are the value of the frro
+         * @param configuration The string representation of the config this overlay is enabled for
+         */
+        public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
+                String configuration) {
+            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+            entry.resourceName = resourceName;
+            entry.binaryData = value;
+            entry.configuration = configuration;
+            mEntries.add(entry);
+            return this;
+        }
+
         /** Builds an immutable fabricated overlay. */
         public FabricatedOverlay build() {
             final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index ff07291..09d24d4 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -40,8 +40,10 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableContainer;
 import android.icu.text.PluralRules;
+import android.net.Uri;
 import android.os.Build;
 import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -59,6 +61,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
@@ -799,7 +803,21 @@
     private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
             @NonNull Resources wrapper, @NonNull TypedValue value) {
         ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
-                            wrapper, value);
+                wrapper, value);
+        try {
+            return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+        } catch (IOException ioe) {
+            // This is okay. This may be something that ImageDecoder does not
+            // support, like SVG.
+            return null;
+        }
+    }
+
+    @Nullable
+    private Drawable decodeImageDrawable(@NonNull FileInputStream fis, @NonNull Resources wrapper) {
+        ImageDecoder.Source src = ImageDecoder.createSource(wrapper, fis);
         try {
             return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
@@ -860,6 +878,17 @@
                     } else {
                         dr = loadXmlDrawable(wrapper, value, id, density, file);
                     }
+                } else if (file.startsWith("frro://")) {
+                    Uri uri = Uri.parse(file);
+                    File f = new File('/' + uri.getHost() + uri.getPath());
+                    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(f,
+                            ParcelFileDescriptor.MODE_READ_ONLY);
+                    AssetFileDescriptor afd = new AssetFileDescriptor(
+                            pfd,
+                            Long.parseLong(uri.getQueryParameter("offset")),
+                            Long.parseLong(uri.getQueryParameter("size")));
+                    FileInputStream is = afd.createInputStream();
+                    dr = decodeImageDrawable(is, wrapper);
                 } else {
                     final InputStream is = mAssets.openNonAsset(
                             value.assetCookie, file, AssetManager.ACCESS_STREAMING);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 24628cd..c5e381d 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -54,7 +54,7 @@
 // The version should only be changed when a backwards-incompatible change must be made to the
 // fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
 // to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index bb918d5..7ec167b 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Binder;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ShellCommand;
@@ -63,7 +64,8 @@
     private final IOverlayManager mInterface;
     private static final Map<String, Integer> TYPE_MAP = Map.of(
             "color", TypedValue.TYPE_FIRST_COLOR_INT,
-            "string", TypedValue.TYPE_STRING);
+            "string", TypedValue.TYPE_STRING,
+            "drawable", -1);
 
     OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
         mContext = ctx;
@@ -257,7 +259,7 @@
         String name = "";
         String filename = null;
         String opt;
-        String configuration = null;
+        String config = null;
         while ((opt = getNextOption()) != null) {
             switch (opt) {
                 case "--user":
@@ -276,7 +278,7 @@
                     filename = getNextArgRequired();
                     break;
                 case "--config":
-                    configuration = getNextArgRequired();
+                    config = getNextArgRequired();
                     break;
                 default:
                     err.println("Error: Unknown option: " + opt);
@@ -311,7 +313,9 @@
             final String resourceName = getNextArgRequired();
             final String typeStr = getNextArgRequired();
             final String strData = String.join(" ", peekRemainingArgs());
-            addOverlayValue(overlayBuilder, resourceName, typeStr, strData, configuration);
+            if (addOverlayValue(overlayBuilder, resourceName, typeStr, strData, config) != 0) {
+                return 1;
+            }
         }
 
         mInterface.commit(new OverlayManagerTransaction.Builder()
@@ -368,8 +372,10 @@
                             return 1;
                         }
                         String config = parser.getAttributeValue(null, "config");
-                        addOverlayValue(overlayBuilder, targetPackage + ':' + target,
-                                overlayType, value, config);
+                        if (addOverlayValue(overlayBuilder, targetPackage + ':' + target,
+                                  overlayType, value, config) != 0) {
+                            return 1;
+                        }
                     }
                 }
             }
@@ -383,7 +389,7 @@
         return 0;
     }
 
-    private void addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
+    private int addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
             String resourceName, String typeString, String valueString, String configuration) {
         final int type;
         typeString = typeString.toLowerCase(Locale.getDefault());
@@ -398,6 +404,9 @@
         }
         if (type == TypedValue.TYPE_STRING) {
             overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
+        } else if (type < 0) {
+            ParcelFileDescriptor pfd =  openFileForSystem(valueString, "r");
+            overlayBuilder.setResourceValue(resourceName, pfd, configuration);
         } else {
             final int intData;
             if (valueString.startsWith("0x")) {
@@ -407,6 +416,7 @@
             }
             overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
         }
+        return 0;
     }
 
     private int runEnableExclusive() throws RemoteException {