Make gainmap parcelable.

Bug: 267215989
Test: android.graphics.cts.GainmapTest

Change-Id: I189f652f26f2f92e37afdc3f69ae7b7653d69bf0
diff --git a/core/api/current.txt b/core/api/current.txt
index d54290b..4b160d9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15497,8 +15497,9 @@
     ctor @Deprecated public EmbossMaskFilter(float[], float, float, float);
   }
 
-  public final class Gainmap {
+  public final class Gainmap implements android.os.Parcelable {
     ctor public Gainmap(@NonNull android.graphics.Bitmap);
+    method public int describeContents();
     method @NonNull public float getDisplayRatioForFullHdr();
     method @NonNull public float[] getEpsilonHdr();
     method @NonNull public float[] getEpsilonSdr();
@@ -15515,6 +15516,8 @@
     method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
     method @NonNull public void setRatioMax(float, float, float);
     method @NonNull public void setRatioMin(float, float, float);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Gainmap> CREATOR;
   }
 
   public class HardwareBufferRenderer implements java.lang.AutoCloseable {
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index b1abc2a1..a39dd08 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -2175,23 +2175,26 @@
 
     public static final @NonNull Parcelable.Creator<Bitmap> CREATOR
             = new Parcelable.Creator<Bitmap>() {
-        /**
-         * Rebuilds a bitmap previously stored with writeToParcel().
-         *
-         * @param p    Parcel object to read the bitmap from
-         * @return a new bitmap created from the data in the parcel
-         */
-        public Bitmap createFromParcel(Parcel p) {
-            Bitmap bm = nativeCreateFromParcel(p);
-            if (bm == null) {
-                throw new RuntimeException("Failed to unparcel Bitmap");
-            }
-            return bm;
-        }
-        public Bitmap[] newArray(int size) {
-            return new Bitmap[size];
-        }
-    };
+                /**
+                 * Rebuilds a bitmap previously stored with writeToParcel().
+                 *
+                 * @param p    Parcel object to read the bitmap from
+                 * @return a new bitmap created from the data in the parcel
+                 */
+                public Bitmap createFromParcel(Parcel p) {
+                    Bitmap bm = nativeCreateFromParcel(p);
+                    if (bm == null) {
+                        throw new RuntimeException("Failed to unparcel Bitmap");
+                    }
+                    if (p.readBoolean()) {
+                        bm.setGainmap(p.readTypedObject(Gainmap.CREATOR));
+                    }
+                    return bm;
+                }
+                public Bitmap[] newArray(int size) {
+                    return new Bitmap[size];
+                }
+            };
 
     /**
      * No special parcel contents.
@@ -2215,6 +2218,12 @@
         if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
             throw new RuntimeException("native writeToParcel failed");
         }
+        if (hasGainmap()) {
+            p.writeBoolean(true);
+            p.writeTypedObject(mGainmap, flags);
+        } else {
+            p.writeBoolean(false);
+        }
     }
 
     /**
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index 53f23c0..470a06c 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -18,6 +18,8 @@
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -76,7 +78,7 @@
  *
  * In the above math, log() is a natural logarithm and exp() is natural exponentiation.
  */
-public final class Gainmap {
+public final class Gainmap implements Parcelable {
 
     // Use a Holder to allow static initialization of Gainmap in the boot image.
     private static class NoImagePreloadHolder {
@@ -284,6 +286,50 @@
         return nGetDisplayRatioSdr(mNativePtr);
     }
 
+    /**
+     * No special parcel contents.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write the gainmap to the parcel.
+     *
+     * @param dest Parcel object to write the gainmap data into
+     * @param flags Additional flags about how the object should be written.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        if (mNativePtr == 0) {
+            throw new IllegalStateException("Cannot be written to a parcel");
+        }
+        dest.writeTypedObject(mGainmapContents, flags);
+        // write gainmapinfo into parcel
+        nWriteGainmapToParcel(mNativePtr, dest);
+    }
+
+    public static final @NonNull Parcelable.Creator<Gainmap> CREATOR =
+            new Parcelable.Creator<Gainmap>() {
+            /**
+             * Rebuilds a gainmap previously stored with writeToParcel().
+             *
+             * @param in Parcel object to read the gainmap from
+             * @return a new gainmap created from the data in the parcel
+             */
+            public Gainmap createFromParcel(Parcel in) {
+                Gainmap gm = new Gainmap(in.readTypedObject(Bitmap.CREATOR));
+                // read gainmapinfo from parcel
+                nReadGainmapFromParcel(gm.mNativePtr, in);
+                return gm;
+            }
+
+            public Gainmap[] newArray(int size) {
+                return new Gainmap[size];
+            }
+        };
+
     private static native long nGetFinalizer();
     private static native long nCreateEmpty();
 
@@ -309,4 +355,6 @@
 
     private static native void nSetDisplayRatioSdr(long ptr, float min);
     private static native float nGetDisplayRatioSdr(long ptr);
+    private static native void nWriteGainmapToParcel(long ptr, Parcel dest);
+    private static native void nReadGainmapFromParcel(long ptr, Parcel src);
 }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 9e3f115..b221654 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -364,6 +364,7 @@
         "jni/PathMeasure.cpp",
         "jni/Picture.cpp",
         "jni/Region.cpp",
+        "jni/ScopedParcel.cpp",
         "jni/Shader.cpp",
         "jni/RenderEffect.cpp",
         "jni/Typeface.cpp",
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index e71a2a5..3f9c4bd 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -10,6 +10,7 @@
 #include "Gainmap.h"
 #include "GraphicsJNI.h"
 #include "HardwareBufferHelpers.h"
+#include "ScopedParcel.h"
 #include "SkBitmap.h"
 #include "SkBlendMode.h"
 #include "SkCanvas.h"
@@ -27,12 +28,7 @@
 
 #ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
 #include <android-base/unique_fd.h>
-#include <android/binder_parcel.h>
-#include <android/binder_parcel_jni.h>
-#include <android/binder_parcel_platform.h>
-#include <cutils/ashmem.h>
 #include <renderthread/RenderProxy.h>
-#include <sys/mman.h>
 #endif
 
 #include <inttypes.h>
@@ -616,91 +612,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // TODO: Move somewhere else
-#ifdef __ANDROID__ // Layoutlib does not support parcel
-
-class ScopedParcel {
-public:
-    explicit ScopedParcel(JNIEnv* env, jobject parcel) {
-        mParcel = AParcel_fromJavaParcel(env, parcel);
-    }
-
-    ~ScopedParcel() { AParcel_delete(mParcel); }
-
-    int32_t readInt32() {
-        int32_t temp = 0;
-        // TODO: This behavior-matches what android::Parcel does
-        // but this should probably be better
-        if (AParcel_readInt32(mParcel, &temp) != STATUS_OK) {
-            temp = 0;
-        }
-        return temp;
-    }
-
-    uint32_t readUint32() {
-        uint32_t temp = 0;
-        // TODO: This behavior-matches what android::Parcel does
-        // but this should probably be better
-        if (AParcel_readUint32(mParcel, &temp) != STATUS_OK) {
-            temp = 0;
-        }
-        return temp;
-    }
-
-    void writeInt32(int32_t value) { AParcel_writeInt32(mParcel, value); }
-
-    void writeUint32(uint32_t value) { AParcel_writeUint32(mParcel, value); }
-
-    bool allowFds() const { return AParcel_getAllowFds(mParcel); }
-
-    std::optional<sk_sp<SkData>> readData() {
-        struct Data {
-            void* ptr = nullptr;
-            size_t size = 0;
-        } data;
-        auto error = AParcel_readByteArray(mParcel, &data,
-                                           [](void* arrayData, int32_t length,
-                                              int8_t** outBuffer) -> bool {
-                                               Data* data = reinterpret_cast<Data*>(arrayData);
-                                               if (length > 0) {
-                                                   data->ptr = sk_malloc_canfail(length);
-                                                   if (!data->ptr) {
-                                                       return false;
-                                                   }
-                                                   *outBuffer =
-                                                           reinterpret_cast<int8_t*>(data->ptr);
-                                                   data->size = length;
-                                               }
-                                               return true;
-                                           });
-        if (error != STATUS_OK || data.size <= 0) {
-            sk_free(data.ptr);
-            return std::nullopt;
-        } else {
-            return SkData::MakeFromMalloc(data.ptr, data.size);
-        }
-    }
-
-    void writeData(const std::optional<sk_sp<SkData>>& optData) {
-        if (optData) {
-            const auto& data = *optData;
-            AParcel_writeByteArray(mParcel, reinterpret_cast<const int8_t*>(data->data()),
-                                   data->size());
-        } else {
-            AParcel_writeByteArray(mParcel, nullptr, -1);
-        }
-    }
-
-    AParcel* get() { return mParcel; }
-
-private:
-    AParcel* mParcel;
-};
-
-enum class BlobType : int32_t {
-    IN_PLACE,
-    ASHMEM,
-};
-
+#ifdef __ANDROID__  // Layoutlib does not support parcel
 #define ON_ERROR_RETURN(X) \
     if ((error = (X)) != STATUS_OK) return error
 
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index 9cd3fb0..0f8a85d 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -16,8 +16,13 @@
 
 #include <Gainmap.h>
 
+#ifdef __ANDROID__
+#include <binder/Parcel.h>
+#endif
+
 #include "Bitmap.h"
 #include "GraphicsJNI.h"
+#include "ScopedParcel.h"
 #include "graphics_jni_helpers.h"
 
 namespace android {
@@ -154,6 +159,81 @@
     return fromJava(gainmapPtr)->info.fDisplayRatioSdr;
 }
 
+// ----------------------------------------------------------------------------
+// Serialization
+// ----------------------------------------------------------------------------
+
+static void Gainmap_writeToParcel(JNIEnv* env, jobject, jlong nativeObject, jobject parcel) {
+#ifdef __ANDROID__  // Layoutlib does not support parcel
+    if (parcel == NULL) {
+        ALOGD("write null parcel\n");
+        return;
+    }
+    ScopedParcel p(env, parcel);
+    SkGainmapInfo info = fromJava(nativeObject)->info;
+    // write gainmap to parcel
+    // ratio min
+    p.writeFloat(info.fGainmapRatioMin.fR);
+    p.writeFloat(info.fGainmapRatioMin.fG);
+    p.writeFloat(info.fGainmapRatioMin.fB);
+    // ratio max
+    p.writeFloat(info.fGainmapRatioMax.fR);
+    p.writeFloat(info.fGainmapRatioMax.fG);
+    p.writeFloat(info.fGainmapRatioMax.fB);
+    // gamma
+    p.writeFloat(info.fGainmapGamma.fR);
+    p.writeFloat(info.fGainmapGamma.fG);
+    p.writeFloat(info.fGainmapGamma.fB);
+    // epsilonsdr
+    p.writeFloat(info.fEpsilonSdr.fR);
+    p.writeFloat(info.fEpsilonSdr.fG);
+    p.writeFloat(info.fEpsilonSdr.fB);
+    // epsilonhdr
+    p.writeFloat(info.fEpsilonHdr.fR);
+    p.writeFloat(info.fEpsilonHdr.fG);
+    p.writeFloat(info.fEpsilonHdr.fB);
+    // display ratio sdr
+    p.writeFloat(info.fDisplayRatioSdr);
+    // display ratio hdr
+    p.writeFloat(info.fDisplayRatioHdr);
+    // base image type
+    p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
+    // type
+    p.writeInt32(static_cast<int32_t>(info.fType));
+#else
+    doThrowRE(env, "Cannot use parcels outside of Android!");
+#endif
+}
+
+static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, jobject parcel) {
+#ifdef __ANDROID__  // Layoutlib does not support parcel
+    if (parcel == NULL) {
+        jniThrowNullPointerException(env, "parcel cannot be null");
+        return;
+    }
+    ScopedParcel p(env, parcel);
+
+    SkGainmapInfo info;
+    info.fGainmapRatioMin = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+    info.fGainmapRatioMax = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+    info.fGainmapGamma = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+    info.fEpsilonSdr = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+    info.fEpsilonHdr = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+    info.fDisplayRatioSdr = p.readFloat();
+    info.fDisplayRatioHdr = p.readFloat();
+    info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
+    info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
+
+    fromJava(nativeObject)->info = info;
+#else
+    jniThrowRuntimeException(env, "Cannot use parcels outside of Android");
+#endif
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
 static const JNINativeMethod gGainmapMethods[] = {
         {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
         {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
@@ -172,6 +252,8 @@
         {"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr},
         {"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr},
         {"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr},
+        {"nWriteGainmapToParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_writeToParcel},
+        {"nReadGainmapFromParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_readFromParcel},
 };
 
 int register_android_graphics_Gainmap(JNIEnv* env) {
diff --git a/libs/hwui/jni/ScopedParcel.cpp b/libs/hwui/jni/ScopedParcel.cpp
new file mode 100644
index 0000000..b0f5423
--- /dev/null
+++ b/libs/hwui/jni/ScopedParcel.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#include "ScopedParcel.h"
+
+#ifdef __ANDROID__  // Layoutlib does not support parcel
+
+using namespace android;
+
+int32_t ScopedParcel::readInt32() {
+    int32_t temp = 0;
+    // TODO: This behavior-matches what android::Parcel does
+    // but this should probably be better
+    if (AParcel_readInt32(mParcel, &temp) != STATUS_OK) {
+        temp = 0;
+    }
+    return temp;
+}
+
+uint32_t ScopedParcel::readUint32() {
+    uint32_t temp = 0;
+    // TODO: This behavior-matches what android::Parcel does
+    // but this should probably be better
+    if (AParcel_readUint32(mParcel, &temp) != STATUS_OK) {
+        temp = 0;
+    }
+    return temp;
+}
+
+float ScopedParcel::readFloat() {
+    float temp = 0.;
+    if (AParcel_readFloat(mParcel, &temp) != STATUS_OK) {
+        temp = 0.;
+    }
+    return temp;
+}
+
+std::optional<sk_sp<SkData>> ScopedParcel::readData() {
+    struct Data {
+        void* ptr = nullptr;
+        size_t size = 0;
+    } data;
+    auto error = AParcel_readByteArray(
+            mParcel, &data, [](void* arrayData, int32_t length, int8_t** outBuffer) -> bool {
+                Data* data = reinterpret_cast<Data*>(arrayData);
+                if (length > 0) {
+                    data->ptr = sk_malloc_canfail(length);
+                    if (!data->ptr) {
+                        return false;
+                    }
+                    *outBuffer = reinterpret_cast<int8_t*>(data->ptr);
+                    data->size = length;
+                }
+                return true;
+            });
+    if (error != STATUS_OK || data.size <= 0) {
+        sk_free(data.ptr);
+        return std::nullopt;
+    } else {
+        return SkData::MakeFromMalloc(data.ptr, data.size);
+    }
+}
+
+void ScopedParcel::writeData(const std::optional<sk_sp<SkData>>& optData) {
+    if (optData) {
+        const auto& data = *optData;
+        AParcel_writeByteArray(mParcel, reinterpret_cast<const int8_t*>(data->data()),
+                               data->size());
+    } else {
+        AParcel_writeByteArray(mParcel, nullptr, -1);
+    }
+}
+#endif  // __ANDROID__ // Layoutlib does not support parcel
diff --git a/libs/hwui/jni/ScopedParcel.h b/libs/hwui/jni/ScopedParcel.h
new file mode 100644
index 0000000..fd8d6a2
--- /dev/null
+++ b/libs/hwui/jni/ScopedParcel.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#include "SkData.h"
+
+#ifdef __ANDROID__  // Layoutlib does not support parcel
+#include <android-base/unique_fd.h>
+#include <android/binder_parcel.h>
+#include <android/binder_parcel_jni.h>
+#include <android/binder_parcel_platform.h>
+#include <cutils/ashmem.h>
+#include <renderthread/RenderProxy.h>
+
+class ScopedParcel {
+public:
+    explicit ScopedParcel(JNIEnv* env, jobject parcel) {
+        mParcel = AParcel_fromJavaParcel(env, parcel);
+    }
+
+    ~ScopedParcel() { AParcel_delete(mParcel); }
+
+    int32_t readInt32();
+
+    uint32_t readUint32();
+
+    float readFloat();
+
+    void writeInt32(int32_t value) { AParcel_writeInt32(mParcel, value); }
+
+    void writeUint32(uint32_t value) { AParcel_writeUint32(mParcel, value); }
+
+    void writeFloat(float value) { AParcel_writeFloat(mParcel, value); }
+
+    bool allowFds() const { return AParcel_getAllowFds(mParcel); }
+
+    std::optional<sk_sp<SkData>> readData();
+
+    void writeData(const std::optional<sk_sp<SkData>>& optData);
+
+    AParcel* get() { return mParcel; }
+
+private:
+    AParcel* mParcel;
+};
+
+enum class BlobType : int32_t {
+    IN_PLACE,
+    ASHMEM,
+};
+
+#endif  // __ANDROID__ // Layoutlib does not support parcel
\ No newline at end of file