Add EXIF support to JPEG/R Java encoding API

Bug: b/299202809
Test: YuvImageTest.java
Change-Id: If1aa9598f75062e7d0684d5d0f4b60f1e4a19f4d
diff --git a/core/api/current.txt b/core/api/current.txt
index cb293c8..65f2a71 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16932,6 +16932,7 @@
     ctor public YuvImage(@NonNull byte[], int, int, int, @Nullable int[], @NonNull android.graphics.ColorSpace);
     method public boolean compressToJpeg(android.graphics.Rect, int, java.io.OutputStream);
     method public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream);
+    method @FlaggedApi("com.android.graphics.flags.yuv_image_compress_to_ultra_hdr") public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream, @NonNull byte[]);
     method @NonNull public android.graphics.ColorSpace getColorSpace();
     method public int getHeight();
     method public int[] getStrides();
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index 9a0a22a..6c81a60 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -5,4 +5,11 @@
      namespace: "core_graphics"
      description: "Add a function without unused exact param for computeBounds."
      bug: "304478551"
+}
+
+flag {
+     name: "yuv_image_compress_to_ultra_hdr"
+     namespace: "core_graphics"
+     description: "Feature flag for YUV image compress to Ultra HDR."
+     bug: "308978825"
 }
\ No newline at end of file
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index 6b5238b..b3bed00 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -16,6 +16,9 @@
 
 package android.graphics;
 
+import com.android.graphics.flags.Flags;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import java.io.OutputStream;
@@ -243,6 +246,36 @@
                 new byte[WORKING_COMPRESS_STORAGE]);
     }
 
+  /**
+   * Compress the HDR image into JPEG/R format.
+   *
+   * Sample usage:
+   *     hdr_image.compressToJpegR(sdr_image, 90, stream);
+   *
+   * For the SDR image, only YUV_420_888 image format is supported, and the following
+   * color spaces are supported:
+   *     ColorSpace.Named.SRGB,
+   *     ColorSpace.Named.DISPLAY_P3
+   *
+   * For the HDR image, only YCBCR_P010 image format is supported, and the following
+   * color spaces are supported:
+   *     ColorSpace.Named.BT2020_HLG,
+   *     ColorSpace.Named.BT2020_PQ
+   *
+   * @param sdr       The SDR image, only ImageFormat.YUV_420_888 is supported.
+   * @param quality   Hint to the compressor, 0-100. 0 meaning compress for
+   *                  small size, 100 meaning compress for max quality.
+   * @param stream    OutputStream to write the compressed data.
+   * @return          True if the compression is successful.
+   * @throws IllegalArgumentException if input images are invalid; quality is not within [0,
+   *                  100]; or stream is null.
+   */
+    public boolean compressToJpegR(@NonNull YuvImage sdr, int quality,
+            @NonNull OutputStream stream) {
+        byte[] emptyExif = new byte[0];
+        return compressToJpegR(sdr, quality, stream, emptyExif);
+    }
+
     /**
      * Compress the HDR image into JPEG/R format.
      *
@@ -263,12 +296,14 @@
      * @param quality   Hint to the compressor, 0-100. 0 meaning compress for
      *                  small size, 100 meaning compress for max quality.
      * @param stream    OutputStream to write the compressed data.
+     * @param exif      Exchangeable image file format.
      * @return          True if the compression is successful.
      * @throws IllegalArgumentException if input images are invalid; quality is not within [0,
      *                  100]; or stream is null.
      */
+    @FlaggedApi(Flags.FLAG_YUV_IMAGE_COMPRESS_TO_ULTRA_HDR)
     public boolean compressToJpegR(@NonNull YuvImage sdr, int quality,
-            @NonNull OutputStream stream) {
+            @NonNull OutputStream stream, @NonNull byte[] exif) {
         if (sdr == null) {
             throw new IllegalArgumentException("SDR input cannot be null");
         }
@@ -304,7 +339,7 @@
       return nativeCompressToJpegR(mData, mColorSpace.getDataSpace(),
                                    sdr.getYuvData(), sdr.getColorSpace().getDataSpace(),
                                    mWidth, mHeight, quality, stream,
-                                   new byte[WORKING_COMPRESS_STORAGE]);
+                                   new byte[WORKING_COMPRESS_STORAGE], exif);
   }
 
 
@@ -416,5 +451,5 @@
 
     private static native boolean nativeCompressToJpegR(byte[] hdr, int hdrColorSpaceId,
             byte[] sdr, int sdrColorSpaceId, int width, int height, int quality,
-            OutputStream stream, byte[] tempStorage);
+            OutputStream stream, byte[] tempStorage, byte[] exif);
 }
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 4dbfa88..8c5cc30 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -332,7 +332,7 @@
 
 bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
         SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
-        int width, int height, int jpegQuality) {
+        int width, int height, int jpegQuality, ScopedByteArrayRO* jExif) {
     // Check SDR color space. Now we only support SRGB transfer function
     if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) !=  ADataSpace::TRANSFER_SRGB) {
         jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
@@ -365,6 +365,10 @@
     yuv420.height = height;
     yuv420.colorGamut = sdrColorGamut;
 
+    jpegr_exif_struct exif;
+    exif.data = const_cast<void*>(reinterpret_cast<const void*>(jExif->get()));
+    exif.length = jExif->size();
+
     jpegr_compressed_struct jpegR;
     jpegR.maxLength = width * height * sizeof(uint8_t);
 
@@ -373,7 +377,8 @@
 
     if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
             hdrTransferFunction,
-            &jpegR, jpegQuality, nullptr); success != android::OK) {
+            &jpegR, jpegQuality,
+            exif.length > 0 ? &exif : NULL); success != android::OK) {
         ALOGW("Encode JPEG/R failed, error code: %d.", success);
         return false;
     }
@@ -415,15 +420,17 @@
 static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
         jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
         jint width, jint height, jint quality, jobject jstream,
-        jbyteArray jstorage) {
+        jbyteArray jstorage, jbyteArray jExif) {
     jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
     jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
+    ScopedByteArrayRO exif(env, jExif);
+
     SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
     P010Yuv420ToJpegREncoder encoder;
 
     jboolean result = JNI_FALSE;
     if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
-                       width, height, quality)) {
+                       width, height, quality, &exif)) {
         result = JNI_TRUE;
     }
 
@@ -437,7 +444,7 @@
 static const JNINativeMethod gYuvImageMethods[] = {
     {   "nativeCompressToJpeg",  "([BIII[I[IILjava/io/OutputStream;[B)Z",
         (void*)YuvImage_compressToJpeg },
-    {   "nativeCompressToJpegR",  "([BI[BIIIILjava/io/OutputStream;[B)Z",
+    {   "nativeCompressToJpegR",  "([BI[BIIIILjava/io/OutputStream;[B[B)Z",
         (void*)YuvImage_compressToJpegR }
 };
 
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index 8ef7805..f3f2c65 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -2,6 +2,7 @@
 #define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
 
 #include <android/data_space.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <ultrahdr/jpegr.h>
 
 extern "C" {
@@ -90,11 +91,12 @@
      *  @param width Width of the Yuv data in terms of pixels.
      *  @param height Height of the Yuv data in terms of pixels.
      *  @param jpegQuality Picture quality in [0, 100].
+     *  @param exif Buffer holds EXIF package.
      *  @return true if successfully compressed the stream.
      */
     bool encode(JNIEnv* env,
             SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
-            int width, int height, int jpegQuality);
+            int width, int height, int jpegQuality, ScopedByteArrayRO* exif);
 
     /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut
      *  used in JPEG/R