Handle 9patches being used for frros

This adds an api that allows creation of nine patch frros.
We process them the same as aapt2 does so that
when they are used at runtime they work correctly.

API-Coverage-Bug: 314168567
Test: manual and automatic
Bug: 296324826
Change-Id: I40da020189e9ec914fbea0c17f181209347d83de
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 55ec7da..6e51f00 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -86,6 +86,7 @@
             static_libs: [
                 "libidmap2_policies",
                 "libidmap2_protos",
+                "libpng",
             ],
             shared_libs: [
                 "libandroidfw",
@@ -107,6 +108,7 @@
                 "libcutils",
                 "libidmap2_policies",
                 "libidmap2_protos",
+                "libpng",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
@@ -185,6 +187,7 @@
     static_libs: [
         "libgmock",
         "libidmap2_protos",
+        "libpng",
     ],
     target: {
         android: {
@@ -258,6 +261,7 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
+                "libpng",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
@@ -275,6 +279,7 @@
                 "libidmap2",
                 "libidmap2_policies",
                 "liblog",
+                "libpng",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libziparchive",
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index d76ca5b..f264125 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -266,7 +266,8 @@
     } else if (res.binaryData.has_value()) {
       builder.SetResourceValue(res.resourceName, res.binaryData->get(),
                                res.binaryDataOffset, res.binaryDataSize,
-                               res.configuration.value_or(std::string()));
+                               res.configuration.value_or(std::string()),
+                               res.isNinePatch);
     } else {
       builder.SetResourceValue(res.resourceName, res.dataType, res.data,
             res.configuration.value_or(std::string()));
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index 8ebd454..bca2ff3 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -28,4 +28,5 @@
     @nullable @utf8InCpp String configuration;
     long binaryDataOffset;
     long binaryDataSize;
+    boolean isNinePatch;
 }
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 1e7d4c2..bfcd4b9 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -19,6 +19,8 @@
 
 #include <libidmap2/proto/fabricated_v1.pb.h>
 
+#include "androidfw/Streams.h"
+
 #include <istream>
 #include <map>
 #include <memory>
@@ -51,7 +53,8 @@
                               std::optional<android::base::borrowed_fd>&& binary_value,
                               off64_t data_binary_offset,
                               size_t data_binary_size,
-                              const std::string& configuration);
+                              const std::string& configuration,
+                              bool nine_patch);
 
     inline Builder& setFrroPath(std::string frro_path) {
       frro_path_ = std::move(frro_path);
@@ -70,6 +73,7 @@
       off64_t data_binary_offset;
       size_t data_binary_size;
       std::string configuration;
+      bool nine_patch;
     };
 
     std::string package_name_;
@@ -81,7 +85,7 @@
   };
 
   struct BinaryData {
-    android::base::borrowed_fd file_descriptor;
+    std::unique_ptr<android::InputStream> input_stream;
     off64_t offset;
     size_t size;
   };
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index d4490ef4..9e463c9 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -45,6 +45,7 @@
   std::optional<android::base::borrowed_fd> data_binary_value;
   off64_t data_binary_offset;
   size_t data_binary_size;
+  bool nine_patch;
 };
 
 struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 47daf23..16bb896 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -20,8 +20,16 @@
 #include <sys/types.h>  // umask
 
 #include <android-base/file.h>
+#include <android-base/strings.h>
+#include <androidfw/BigBuffer.h>
+#include <androidfw/BigBufferStream.h>
+#include <androidfw/FileStream.h>
+#include <androidfw/Image.h>
+#include <androidfw/Png.h>
 #include <androidfw/ResourceUtils.h>
+#include <androidfw/StringPiece.h>
 #include <androidfw/StringPool.h>
+#include <androidfw/Streams.h>
 #include <google/protobuf/io/coded_stream.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <utils/ByteOrder.h>
@@ -32,9 +40,9 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <sys/utsname.h>
 
 namespace android::idmap2 {
-
 constexpr auto kBufferSize = 1024;
 
 namespace {
@@ -81,7 +89,7 @@
     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, "", std::nullopt, 0, 0, configuration});
+      Entry{resource_name, data_type, data_value, "", std::nullopt, 0, 0, configuration, false});
   return *this;
 }
 
@@ -89,18 +97,90 @@
     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, std::nullopt, 0, 0, configuration});
+      Entry{resource_name,
+            data_type,
+            0,
+            data_string_value,
+            std::nullopt,
+            0,
+            0,
+            configuration,
+            false});
   return *this;
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
-    off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration) {
+    off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration,
+    bool nine_patch) {
   entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value,
-                              data_binary_offset, data_binary_size, configuration});
+                              data_binary_offset, data_binary_size, configuration, nine_patch});
   return *this;
 }
 
+static Result<FabricatedOverlay::BinaryData> buildBinaryData(
+        pb::ResourceValue* pb_value, const TargetValue &value) {
+  pb_value->set_data_type(Res_value::TYPE_STRING);
+  size_t binary_size;
+  off64_t binary_offset;
+  std::unique_ptr<android::InputStream> binary_stream;
+
+  if (value.nine_patch) {
+    std::string file_contents;
+    file_contents.resize(value.data_binary_size);
+    if (!base::ReadFullyAtOffset(value.data_binary_value->get(), file_contents.data(),
+                                 value.data_binary_size, value.data_binary_offset)) {
+      return Error("Failed to read binary file data.");
+    }
+    const StringPiece content(file_contents.c_str(), file_contents.size());
+    android::PngChunkFilter png_chunk_filter(content);
+    android::AndroidLogDiagnostics diag;
+    auto png = android::ReadPng(&png_chunk_filter, &diag);
+    if (!png) {
+      return Error("Error opening file as png");
+    }
+
+    std::string err;
+    std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(png->rows.get(),
+                                                              png->width, png->height,
+                                                              &err);
+    if (!nine_patch) {
+      return Error("%s", err.c_str());
+    }
+
+    png->width -= 2;
+    png->height -= 2;
+    memmove(png->rows.get(), png->rows.get() + 1, png->height * sizeof(uint8_t**));
+    for (int32_t h = 0; h < png->height; h++) {
+      memmove(png->rows[h], png->rows[h] + 4, png->width * 4);
+    }
+
+    android::BigBuffer buffer(value.data_binary_size);
+    android::BigBufferOutputStream buffer_output_stream(&buffer);
+    if (!android::WritePng(png.get(), nine_patch.get(), &buffer_output_stream, {},
+                           &diag, false)) {
+      return Error("Error writing frro png");
+    }
+
+    binary_size = buffer.size();
+    binary_offset = 0;
+    android::BigBufferInputStream *buffer_input_stream
+            = new android::BigBufferInputStream(std::move(buffer));
+    binary_stream.reset(buffer_input_stream);
+  } else {
+    binary_size = value.data_binary_size;
+    binary_offset = value.data_binary_offset;
+    android::FileInputStream *fis
+            = new android::FileInputStream(value.data_binary_value.value());
+    binary_stream.reset(fis);
+  }
+
+  return FabricatedOverlay::BinaryData{
+          std::move(binary_stream),
+          binary_offset,
+          binary_size};
+}
+
 Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
   using ConfigMap = std::map<std::string, TargetValue, std::less<>>;
   using EntryMap = std::map<std::string, ConfigMap, std::less<>>;
@@ -150,7 +230,8 @@
 
     value->second = TargetValue{res_entry.data_type, res_entry.data_value,
                                 res_entry.data_string_value, res_entry.data_binary_value,
-                                res_entry.data_binary_offset, res_entry.data_binary_size};
+                                res_entry.data_binary_offset, res_entry.data_binary_size,
+                                res_entry.nine_patch};
   }
 
   pb::FabricatedOverlay overlay_pb;
@@ -183,18 +264,20 @@
             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);
-              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> (value.second.data_binary_size));
-              total_binary_bytes += value.second.data_binary_size;
-              binary_files.emplace_back(FabricatedOverlay::BinaryData{
-                  value.second.data_binary_value->get(),
-                  value.second.data_binary_offset,
-                  value.second.data_binary_size});
-              auto ref = string_pool.MakeRef(std::move(uri));
-              pb_value->set_data_value(ref.index());
+            auto binary_data = buildBinaryData(pb_value, value.second);
+            if (!binary_data) {
+              return binary_data.GetError();
+            }
+            pb_value->set_data_type(Res_value::TYPE_STRING);
+
+            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> (binary_data->size));
+            total_binary_bytes += binary_data->size;
+            binary_files.emplace_back(std::move(*binary_data));
+            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);
           }
@@ -311,9 +394,9 @@
   Write32(stream, (*data)->pb_crc);
   Write32(stream, total_binary_bytes_);
   std::string file_contents;
-  for (const FabricatedOverlay::BinaryData fd : binary_files_) {
-    file_contents.resize(fd.size);
-    if (!ReadFullyAtOffset(fd.file_descriptor, file_contents.data(), fd.size, fd.offset)) {
+  for (const FabricatedOverlay::BinaryData& bd : binary_files_) {
+    file_contents.resize(bd.size);
+    if (!bd.input_stream->ReadFullyAtOffset(file_contents.data(), bd.size, bd.offset)) {
       return Error("Failed to read binary file data.");
     }
     stream.write(file_contents.data(), file_contents.length());
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
index c7f5cf3..7f9c468 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -53,7 +53,7 @@
         if (entry_params.data_binary_value.has_value()) {
             builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value,
                                      entry_params.binary_data_offset, entry_params.binary_data_size,
-                                     entry_params.configuration);
+                                     entry_params.configuration, entry_params.nine_patch);
         } else  if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) {
            builder.SetResourceValue(entry_params.resource_name, dataType,
                                     entry_params.data_value, entry_params.configuration);
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index b460bb3..6b1c7e8 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -59,7 +59,7 @@
               Res_value::TYPE_STRING,
               "foobar",
               "en-rUS-normal-xxhdpi-v21")
-          .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7")
+          .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7", false)
           .setFrroPath("/foo/bar/biz.frro")
           .Build();
   ASSERT_TRUE(overlay);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index a3448fd..a384305 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -269,7 +269,7 @@
                   .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, 0, 8341, "port-xxhdpi-v7")
+                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7", false)
                   .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 40f98c2..db44c23 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -212,7 +212,7 @@
                   .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, 0, 8341, "")
+                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "", false)
                   .setFrroPath("/foo/bar/biz.frro")
                   .Build();