Introduce Builder in ImageReader class for setup and construct an
ImageReader instance.

- Builder pattern allows the app to provide HardwareBuffer.Format
constant which is 1:1 mapping with the HAL PixelFormat
- create an JNI binding for PublicFormat.cpp functions and directly map
imageFormat to the pairings of hardwareBufferformat and dataspace
in the ImageReader class.
- involve dataspace setting option into ImageReader

Bug: 205734633
Test: android.hardware.camera2.cts.ImageReaderTest pass
Change-Id: Idd4c610a710d123615449af76763f1c04afb2bda
diff --git a/core/api/current.txt b/core/api/current.txt
index 020c8ff..bdedc58 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21681,16 +21681,29 @@
     method public android.media.Image acquireNextImage();
     method public void close();
     method public void discardFreeBuffers();
+    method public long getDataSpace();
+    method public int getHardwareBufferFormat();
     method public int getHeight();
     method public int getImageFormat();
     method public int getMaxImages();
     method public android.view.Surface getSurface();
+    method public long getUsage();
     method public int getWidth();
     method @NonNull public static android.media.ImageReader newInstance(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
     method @NonNull public static android.media.ImageReader newInstance(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int, long);
     method public void setOnImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler);
   }
 
+  public static final class ImageReader.Builder {
+    ctor public ImageReader.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
+    method @NonNull public android.media.ImageReader build();
+    method @NonNull public android.media.ImageReader.Builder setDefaultDataSpace(long);
+    method @NonNull public android.media.ImageReader.Builder setDefaultHardwareBufferFormat(int);
+    method @NonNull public android.media.ImageReader.Builder setImageFormat(int);
+    method @NonNull public android.media.ImageReader.Builder setMaxImages(int);
+    method @NonNull public android.media.ImageReader.Builder setUsage(long);
+  }
+
   public static interface ImageReader.OnImageAvailableListener {
     method public void onImageAvailable(android.media.ImageReader);
   }
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index bd0f32e..69ce8d2 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -18,10 +18,13 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.graphics.GraphicBuffer;
 import android.graphics.ImageFormat;
 import android.graphics.ImageFormat.Format;
 import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
 import android.hardware.HardwareBuffer;
 import android.hardware.HardwareBuffer.Usage;
 import android.hardware.camera2.MultiResolutionImageReader;
@@ -136,8 +139,7 @@
         // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not
         // work, and is inscrutable anyway
         return new ImageReader(width, height, format, maxImages,
-                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN,
-                /*parent*/ null);
+                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, null);
     }
 
     /**
@@ -268,44 +270,32 @@
         // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not
         // work, and is inscrutable anyway
         return new ImageReader(width, height, format, maxImages,
-                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN,
-                parent);
+                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, parent);
     }
 
-
-    /**
-     * @hide
-     */
-    protected ImageReader(int width, int height, int format, int maxImages, long usage,
-            MultiResolutionImageReader parent) {
-        mWidth = width;
-        mHeight = height;
-        mFormat = format;
-        mUsage = usage;
-        mMaxImages = maxImages;
-        mParent = parent;
-
+    private void initializeImageReader(int width, int height, int imageFormat, int maxImages,
+            long usage, int hardwareBufferFormat, long dataSpace, boolean useLegacyImageFormat) {
         if (width < 1 || height < 1) {
             throw new IllegalArgumentException(
                 "The image dimensions must be positive");
         }
-        if (mMaxImages < 1) {
+
+        if (maxImages < 1) {
             throw new IllegalArgumentException(
                 "Maximum outstanding image count must be at least 1");
         }
 
-        if (format == ImageFormat.NV21) {
+        if (imageFormat == ImageFormat.NV21) {
             throw new IllegalArgumentException(
-                    "NV21 format is not supported");
+                "NV21 format is not supported");
         }
 
-        mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat);
-
-        nativeInit(new WeakReference<>(this), width, height, format, maxImages, usage);
-
-        mSurface = nativeGetSurface();
+        nativeInit(new WeakReference<>(this), width, height, maxImages, usage,
+                hardwareBufferFormat, dataSpace);
 
         mIsReaderValid = true;
+
+        mSurface = nativeGetSurface();
         // Estimate the native buffer allocation size and register it so it gets accounted for
         // during GC. Note that this doesn't include the buffers required by the buffer queue
         // itself and the buffers requested by the producer.
@@ -313,10 +303,46 @@
         // complex, and 1 buffer is enough for the VM to treat the ImageReader as being of some
         // size.
         mEstimatedNativeAllocBytes = ImageUtils.getEstimatedNativeAllocBytes(
-                width, height, format, /*buffer count*/ 1);
+            width, height, useLegacyImageFormat ? imageFormat : hardwareBufferFormat,
+            /*buffer count*/ 1);
         VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
     }
 
+    private ImageReader(int width, int height, int imageFormat, int maxImages, long usage,
+            MultiResolutionImageReader parent) {
+        mWidth = width;
+        mHeight = height;
+        mFormat = imageFormat;
+        mUsage = usage;
+        mMaxImages = maxImages;
+        mParent = parent;
+        // retrieve hal Format and hal dataspace from imageFormat
+        mHardwareBufferFormat = PublicFormatUtils.getHalFormat(mFormat);
+        mDataSpace = PublicFormatUtils.getHalDataspace(mFormat);
+        mUseLegacyImageFormat = true;
+        mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat);
+
+        initializeImageReader(width, height, imageFormat, maxImages, usage, mHardwareBufferFormat,
+                mDataSpace, mUseLegacyImageFormat);
+    }
+
+    private ImageReader(int width, int height, int maxImages, long usage,
+            MultiResolutionImageReader parent, int hardwareBufferFormat, long dataSpace) {
+        mWidth = width;
+        mHeight = height;
+        mFormat = ImageFormat.UNKNOWN; // set default image format value as UNKNOWN
+        mUsage = usage;
+        mMaxImages = maxImages;
+        mParent = parent;
+        mHardwareBufferFormat = hardwareBufferFormat;
+        mDataSpace = dataSpace;
+        mUseLegacyImageFormat = false;
+        mNumPlanes = ImageUtils.getNumPlanesForHardwareBufferFormat(mHardwareBufferFormat);
+
+        initializeImageReader(width, height, mFormat, maxImages, usage, hardwareBufferFormat,
+                dataSpace, mUseLegacyImageFormat);
+    }
+
     /**
      * The default width of {@link Image Images}, in pixels.
      *
@@ -354,6 +380,10 @@
      * As of now, each format is only compatible to itself.
      * The actual format of the images can be found using {@link Image#getFormat}.</p>
      *
+     * <p>Use this function if the ImageReader instance is created by factory method
+     * {@code newInstance} function or by builder pattern {@code ImageReader.Builder} and using
+     * {@link Builder#setImageFormat}.</p>
+     *
      * @return the expected format of an Image
      *
      * @see ImageFormat
@@ -363,6 +393,32 @@
     }
 
     /**
+     * The default {@link HardwareBuffer} format of {@link Image Images}.
+     *
+     * <p>Use this function if the ImageReader instance is created by builder pattern
+     * {@code ImageReader.Builder} and using {@link Builder#setDefaultHardwareBufferFormat} and
+     * {@link Builder#setDefaultDataSpace}.</p>
+     *
+     * @return the expected {@link HardwareBuffer} format of an Image.
+     */
+    public @HardwareBuffer.Format int getHardwareBufferFormat() {
+        return mHardwareBufferFormat;
+    }
+
+    /**
+     * The default dataspace of {@link Image Images}.
+     *
+     * <p>Use this function if the ImageReader instance is created by builder pattern
+     * {@code ImageReader.Builder} and {@link Builder#setDefaultDataSpace}.</p>
+     *
+     * @return the expected dataspace of an Image.
+     */
+    @SuppressLint("MethodNameUnits")
+    public @NamedDataSpace long getDataSpace() {
+        return mDataSpace;
+    }
+
+    /**
      * Maximum number of images that can be acquired from the ImageReader by any time (for example,
      * with {@link #acquireNextImage}).
      *
@@ -384,6 +440,15 @@
     }
 
     /**
+     * The usage flag of images that can be produced by the ImageReader.
+     *
+     * @return The usage flag of the images for this ImageReader.
+     */
+    public @Usage long getUsage() {
+        return mUsage;
+    }
+
+    /**
      * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this
      * {@code ImageReader}.</p>
      *
@@ -469,7 +534,12 @@
      * @hide
      */
     public Image acquireNextImageNoThrowISE() {
-        SurfaceImage si = new SurfaceImage(mFormat);
+        SurfaceImage si;
+        if (mUseLegacyImageFormat) {
+            si = new SurfaceImage(mFormat);
+        } else {
+            si = new SurfaceImage(mHardwareBufferFormat, mDataSpace);
+        }
         return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null;
     }
 
@@ -492,7 +562,7 @@
             // A null image will eventually be returned if ImageReader is already closed.
             int status = ACQUIRE_NO_BUFS;
             if (mIsReaderValid) {
-                status = nativeImageSetup(si);
+                status = nativeImageSetup(si, mUseLegacyImageFormat);
             }
 
             switch (status) {
@@ -545,7 +615,12 @@
     public Image acquireNextImage() {
         // Initialize with reader format, but can be overwritten by native if the image
         // format is different from the reader format.
-        SurfaceImage si = new SurfaceImage(mFormat);
+        SurfaceImage si;
+        if (mUseLegacyImageFormat) {
+            si = new SurfaceImage(mFormat);
+        } else {
+            si = new SurfaceImage(mHardwareBufferFormat, mDataSpace);
+        }
         int status = acquireNextSurfaceImage(si);
 
         switch (status) {
@@ -838,13 +913,161 @@
         }
     }
 
+    /**
+     * Builder class for {@link ImageReader} objects.
+     */
+    public static final class Builder {
+        private int mWidth;
+        private int mHeight;
+        private int mMaxImages = 1;
+        private int mImageFormat = ImageFormat.UNKNOWN;
+        private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+        private long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+        private long mUsage = HardwareBuffer.USAGE_CPU_READ_OFTEN;
+        private boolean mUseLegacyImageFormat = false;
+
+        /**
+         * Constructs a new builder for {@link ImageReader}.
+         *
+         * @param width The default width in pixels that will be passed to the producer.
+         *              May be overridden by the producer.
+         * @param height The default height in pixels that will be passed to the producer.
+         *              May be overridden by the producer.
+         * @see Image
+         */
+        public Builder(@IntRange(from = 1) int width, @IntRange(from = 1) int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        /**
+         * Set the maximal number of images.
+         *
+         * @param maxImages The maximum number of images the user will want to
+         *            access simultaneously. This should be as small as possible to
+         *            limit memory use. Default value is 1.
+         * @return the Builder instance with customized usage value.
+         */
+        public @NonNull Builder setMaxImages(int maxImages) {
+            mMaxImages = maxImages;
+            return this;
+        }
+
+        /**
+         * Set the consumer usage flag.
+         *
+         * @param usage The intended usage of the images consumed by this ImageReader.
+         *              See the usages on {@link HardwareBuffer} for a list of valid usage bits.
+         *              Default value is {@link HardwareBuffer#USAGE_CPU_READ_OFTEN}.
+         * @return the Builder instance with customized usage value.
+         *
+         * @see HardwareBuffer
+         */
+        public @NonNull Builder setUsage(long usage) {
+            mUsage = usage;
+            return this;
+        }
+
+        /**
+         * Set the default image format passed by the producer. May be overridden by the producer.
+         *
+         * <p>{@link #setImageFormat} function replaces the combination of
+         * {@link #setDefaultHardwareBufferFormat} and {@link #setDefaultDataSpace} functions.
+         * Either this or these two functions must be called to initialize an {@code ImageReader}
+         * instance.</p>
+         *
+         * @param imageFormat The format of the image that this reader will produce. This
+         *                    must be one of the {@link android.graphics.ImageFormat} or
+         *                   {@link android.graphics.PixelFormat} constants. Note that not
+         *                   all formats are supported, like ImageFormat.NV21. The default value is
+         *                   {@link ImageFormat#UNKNOWN}.
+         * @return the builder instance with customized image format value.
+         *
+         * @see #setDefaultHardwareBufferFormat
+         * @see #setDefaultDataSpace
+         */
+        public @NonNull Builder setImageFormat(@Format int imageFormat) {
+            mImageFormat = imageFormat;
+            mUseLegacyImageFormat = true;
+            mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+            mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+            return this;
+        }
+
+        /**
+         * Set the default hardwareBuffer format passed by the producer.
+         * May be overridden by the producer.
+         *
+         * <p>This function works together with {@link #setDefaultDataSpace} for an
+         * {@link ImageReader} instance. Setting at least one of these two replaces
+         * {@link #setImageFormat} function.</p>
+         *
+         * <p>The format of the Image can be overridden after {@link #setImageFormat} by calling
+         * this function and then {@link #setDefaultDataSpace} functions.
+         * <i>Warning:</i> Missing one of callings for initializing or overriding the format may
+         * involve undefined behaviors.</p>
+         *
+         * @param hardwareBufferFormat The HardwareBuffer format of the image that this reader
+         *                             will produce. The default value is
+         *                             {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}.
+         * @return the builder instance with customized hardwareBuffer value.
+         *
+         * @see #setDefaultDataSpace
+         * @see #setImageFormat
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder setDefaultHardwareBufferFormat(
+                @HardwareBuffer.Format int hardwareBufferFormat) {
+            mHardwareBufferFormat = hardwareBufferFormat;
+            mUseLegacyImageFormat = false;
+            mImageFormat = ImageFormat.UNKNOWN;
+            return this;
+        }
+
+        /**
+         * Set the default dataspace passed by the producer.
+         * May be overridden by the producer.
+         *
+         * <p>This function works together with {@link #setDefaultHardwareBufferFormat} for an
+         * {@link ImageReader} instance. Setting at least one of these two replaces
+         * {@link #setImageFormat} function.</p>
+         *
+         * @param dataSpace The dataspace of the image that this reader will produce.
+         *                  The default value is {@link DataSpace#DATASPACE_UNKNOWN}.
+         * @return the builder instance with customized dataspace value.
+         *
+         * @see #setDefaultHardwareBufferFormat
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder setDefaultDataSpace(@NamedDataSpace long dataSpace) {
+            mDataSpace = dataSpace;
+            mUseLegacyImageFormat = false;
+            mImageFormat = ImageFormat.UNKNOWN;
+            return this;
+        }
+
+        /**
+         * Builds a new ImageReader object.
+         *
+         * @return The new ImageReader object.
+         */
+        public @NonNull ImageReader build() {
+            if (mUseLegacyImageFormat) {
+                return new ImageReader(mWidth, mHeight, mImageFormat, mMaxImages, mUsage, null);
+            } else {
+                return new ImageReader(mWidth, mHeight, mMaxImages, mUsage, null,
+                    mHardwareBufferFormat, mDataSpace);
+            }
+        }
+    }
+
     private final int mWidth;
     private final int mHeight;
     private final int mFormat;
     private final long mUsage;
     private final int mMaxImages;
     private final int mNumPlanes;
-    private final Surface mSurface;
+    private Surface mSurface;
     private int mEstimatedNativeAllocBytes;
 
     private final Object mListenerLock = new Object();
@@ -861,6 +1084,12 @@
     // MultiResolutionImageReader.
     private final MultiResolutionImageReader mParent;
 
+    private final int mHardwareBufferFormat;
+
+    private final long mDataSpace;
+
+    private final boolean mUseLegacyImageFormat;
+
     /**
      * This field is used by native code, do not access or modify.
      */
@@ -897,6 +1126,12 @@
             mFormat = format;
         }
 
+        SurfaceImage(int hardwareBufferFormat, long dataSpace) {
+            mHardwareBufferFormat = hardwareBufferFormat;
+            mDataSpace = dataSpace;
+            mFormat = PublicFormatUtils.getPublicFormat(mHardwareBufferFormat, mDataSpace);
+        }
+
         @Override
         public void close() {
             ImageReader.this.releaseImage(this);
@@ -909,10 +1144,15 @@
         @Override
         public int getFormat() {
             throwISEIfImageIsInvalid();
-            int readerFormat = ImageReader.this.getImageFormat();
-            // Assume opaque reader always produce opaque images.
-            mFormat = (readerFormat == ImageFormat.PRIVATE) ? readerFormat :
-                nativeGetFormat(readerFormat);
+            // update mFormat only if ImageReader is initialized by factory pattern.
+            // if using builder pattern, mFormat has been updated upon initialization.
+            // no need update here.
+            if (ImageReader.this.mUseLegacyImageFormat) {
+                int readerFormat = ImageReader.this.getImageFormat();
+                // Assume opaque reader always produce opaque images.
+                mFormat = (readerFormat == ImageFormat.PRIVATE) ? readerFormat :
+                    nativeGetFormat(readerFormat);
+            }
             return mFormat;
         }
 
@@ -1125,6 +1365,8 @@
 
         private SurfacePlane[] mPlanes;
         private int mFormat = ImageFormat.UNKNOWN;
+        private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+        private long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
         // If this image is detached from the ImageReader.
         private AtomicBoolean mIsDetached = new AtomicBoolean(false);
 
@@ -1137,8 +1379,8 @@
         private synchronized native HardwareBuffer nativeGetHardwareBuffer();
     }
 
-    private synchronized native void nativeInit(Object weakSelf, int w, int h,
-                                                    int fmt, int maxImgs, long consumerUsage);
+    private synchronized native void nativeInit(Object weakSelf, int w, int h, int maxImgs,
+            long consumerUsage, int hardwareBufferFormat, long dataSpace);
     private synchronized native void nativeClose();
     private synchronized native void nativeReleaseImage(Image i);
     private synchronized native Surface nativeGetSurface();
@@ -1152,7 +1394,7 @@
      * @see #ACQUIRE_NO_BUFS
      * @see #ACQUIRE_MAX_IMAGES
      */
-    private synchronized native int nativeImageSetup(Image i);
+    private synchronized native int nativeImageSetup(Image i, boolean legacyValidateImageFormat);
 
     /**
      * @hide
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index 7837d7e..2f1a36c 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -18,6 +18,7 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
 import android.media.Image.Plane;
 import android.util.Size;
 
@@ -77,6 +78,34 @@
     }
 
     /**
+     * Only a subset of the formats defined in
+     * {@link android.graphics.HardwareBuffer.Format} constants are supported by ImageReader.
+     */
+    public static int getNumPlanesForHardwareBufferFormat(int hardwareBufferFormat) {
+        switch(hardwareBufferFormat) {
+            case HardwareBuffer.YCBCR_420_888:
+                return 3;
+            case HardwareBuffer.RGBA_8888:
+            case HardwareBuffer.RGBX_8888:
+            case HardwareBuffer.RGB_888:
+            case HardwareBuffer.RGB_565:
+            case HardwareBuffer.RGBA_FP16:
+            case HardwareBuffer.RGBA_1010102:
+            case HardwareBuffer.BLOB:
+            case HardwareBuffer.D_16:
+            case HardwareBuffer.D_24:
+            case HardwareBuffer.DS_24UI8:
+            case HardwareBuffer.D_FP32:
+            case HardwareBuffer.DS_FP32UI8:
+            case HardwareBuffer.S_UI8:
+                return 1;
+            default:
+                throw new UnsupportedOperationException(
+                    String.format("Invalid hardwareBuffer format specified %d",
+                            hardwareBufferFormat));
+        }
+    }
+    /**
      * <p>
      * Copy source image data to destination Image.
      * </p>
diff --git a/media/java/android/media/PublicFormatUtils.java b/media/java/android/media/PublicFormatUtils.java
new file mode 100644
index 0000000..6268804
--- /dev/null
+++ b/media/java/android/media/PublicFormatUtils.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 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.media;
+
+/**
+ * Package private utility class for PublicFormat related methods.
+ */
+class PublicFormatUtils {
+    public static int getHalFormat(int imageFormat) {
+        return nativeGetHalFormat(imageFormat);
+    }
+    public static long getHalDataspace(int imageFormat) {
+        return nativeGetHalDataspace(imageFormat);
+    }
+    public static int getPublicFormat(int imageFormat, long dataspace) {
+        return nativeGetPublicFormat(imageFormat, dataspace);
+    }
+    private static native int nativeGetHalFormat(int imageFormat);
+    private static native long nativeGetHalDataspace(int imageFormat);
+    private static native int nativeGetPublicFormat(int imageFormat, long dataspace);
+}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index e817f2d..feae606 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -39,6 +39,7 @@
         "android_media_MediaProfiles.cpp",
         "android_media_MediaRecorder.cpp",
         "android_media_MediaSync.cpp",
+        "android_media_PublicFormatUtils.cpp",
         "android_media_ResampleInputStream.cpp",
         "android_media_Streams.cpp",
         "android_media_SyncParams.cpp",
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 021507c..6002e28 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -375,18 +375,13 @@
 }
 
 static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint width, jint height,
-                             jint format, jint maxImages, jlong ndkUsage)
-{
+                             jint maxImages, jlong ndkUsage, jint nativeFormat, jlong dataSpace) {
     status_t res;
-    int nativeFormat;
-    android_dataspace nativeDataspace;
 
-    ALOGV("%s: width:%d, height: %d, format: 0x%x, maxImages:%d",
-          __FUNCTION__, width, height, format, maxImages);
+    ALOGV("%s: width:%d, height: %d, nativeFormat: %d, maxImages:%d",
+          __FUNCTION__, width, height, nativeFormat, maxImages);
 
-    PublicFormat publicFormat = static_cast<PublicFormat>(format);
-    nativeFormat = mapPublicFormatToHalFormat(publicFormat);
-    nativeDataspace = mapPublicFormatToHalDataspace(publicFormat);
+    android_dataspace nativeDataspace = static_cast<android_dataspace>(dataSpace);
 
     jclass clazz = env->GetObjectClass(thiz);
     if (clazz == NULL) {
@@ -400,7 +395,7 @@
     BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
     sp<BufferItemConsumer> bufferConsumer;
     String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
-            width, height, format, maxImages, getpid(),
+            width, height, nativeFormat, maxImages, getpid(),
             createProcessUniqueId());
     uint64_t consumerUsage =
             android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
@@ -527,7 +522,8 @@
     ALOGV("%s: Image (format: 0x%x) has been released", __FUNCTION__, ctx->getBufferFormat());
 }
 
-static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) {
+static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image,
+                                   jboolean legacyValidateImageFormat) {
     ALOGV("%s:", __FUNCTION__);
     JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
     if (ctx == NULL) {
@@ -590,7 +586,7 @@
             ALOGV("%s: Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
                     __FUNCTION__, outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
         }
-        if (imgReaderFmt != bufferFormat) {
+        if (legacyValidateImageFormat && imgReaderFmt != bufferFormat) {
             if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 &&
                     isPossiblyYUV(bufferFormat)) {
                 // Treat formats that are compatible with flexible YUV
@@ -958,10 +954,10 @@
 
 static const JNINativeMethod gImageReaderMethods[] = {
     {"nativeClassInit",        "()V",                        (void*)ImageReader_classInit },
-    {"nativeInit",             "(Ljava/lang/Object;IIIIJ)V",  (void*)ImageReader_init },
+    {"nativeInit",             "(Ljava/lang/Object;IIIJIJ)V",   (void*)ImageReader_init },
     {"nativeClose",            "()V",                        (void*)ImageReader_close },
     {"nativeReleaseImage",     "(Landroid/media/Image;)V",   (void*)ImageReader_imageRelease },
-    {"nativeImageSetup",       "(Landroid/media/Image;)I",   (void*)ImageReader_imageSetup },
+    {"nativeImageSetup",       "(Landroid/media/Image;Z)I",   (void*)ImageReader_imageSetup },
     {"nativeGetSurface",       "()Landroid/view/Surface;",   (void*)ImageReader_getSurface },
     {"nativeDetachImage",      "(Landroid/media/Image;)I",   (void*)ImageReader_detachImage },
     {"nativeCreateImagePlanes",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 8dcdc98..a548a47 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1454,6 +1454,7 @@
 extern int register_android_media_MediaMuxer(JNIEnv *env);
 extern int register_android_media_MediaRecorder(JNIEnv *env);
 extern int register_android_media_MediaSync(JNIEnv *env);
+extern int register_android_media_PublicFormatUtils(JNIEnv *env);
 extern int register_android_media_ResampleInputStream(JNIEnv *env);
 extern int register_android_media_MediaProfiles(JNIEnv *env);
 extern int register_android_mtp_MtpDatabase(JNIEnv *env);
@@ -1501,6 +1502,11 @@
         goto bail;
     }
 
+    if (register_android_media_PublicFormatUtils(env) < 0) {
+        ALOGE("ERROR: PublicFormatUtils native registration failed\n");
+        goto bail;
+    }
+
     if (register_android_media_ResampleInputStream(env) < 0) {
         ALOGE("ERROR: ResampleInputStream native registration failed\n");
         goto bail;
diff --git a/media/jni/android_media_PublicFormatUtils.cpp b/media/jni/android_media_PublicFormatUtils.cpp
new file mode 100644
index 0000000..09ebdee
--- /dev/null
+++ b/media/jni/android_media_PublicFormatUtils.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#define LOG_TAG "PublicFormatUtils_JNI"
+
+#include <utils/misc.h>
+#include <ui/PublicFormat.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+
+using namespace android;
+
+static jint android_media_PublicFormatUtils_getHalFormat(JNIEnv* /*env*/, jobject /*thiz*/,
+                                                         jint imageFormat) {
+    PublicFormat publicFormat = static_cast<PublicFormat>(imageFormat);
+    int nativeFormat = mapPublicFormatToHalFormat(publicFormat);
+    return static_cast<jint>(nativeFormat);
+}
+
+static jlong android_media_PublicFormatUtils_getHalDataspace(JNIEnv* /*env*/, jobject /*thiz*/,
+                                                             jint imageFormat) {
+    PublicFormat publicFormat = static_cast<PublicFormat>(imageFormat);
+    android_dataspace
+        nativeDataspace = mapPublicFormatToHalDataspace(publicFormat);
+    return static_cast<jlong>(nativeDataspace);
+}
+
+static jint android_media_PublicFormatUtils_getPublicFormat(JNIEnv* /*env*/, jobject /*thiz*/,
+                                                            jint hardwareBufferFormat,
+                                                            jlong dataspace) {
+    PublicFormat nativeFormat = mapHalFormatDataspaceToPublicFormat(
+            hardwareBufferFormat, static_cast<android_dataspace>(dataspace));
+    return static_cast<jint>(nativeFormat);
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nativeGetHalFormat",    "(I)I", (void*)android_media_PublicFormatUtils_getHalFormat},
+    {"nativeGetHalDataspace", "(I)J", (void*)android_media_PublicFormatUtils_getHalDataspace},
+    {"nativeGetPublicFormat", "(IJ)I",(void*)android_media_PublicFormatUtils_getPublicFormat}
+};
+
+int register_android_media_PublicFormatUtils(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+             "android/media/PublicFormatUtils", gMethods, NELEM(gMethods));
+}
+