/*
 * Copyright (C) 2017 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.hardware;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.GraphicBuffer;
import android.os.BadParcelableException;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.SurfaceControl;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import dalvik.system.CloseGuard;

import libcore.util.NativeAllocationRegistry;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * HardwareBuffer wraps a native <code>AHardwareBuffer</code> object, which is a low-level object
 * representing a memory buffer accessible by various hardware units. HardwareBuffer allows sharing
 * buffers across different application processes. In particular, HardwareBuffers may be mappable
 * to memory accessible to various hardware systems, such as the GPU, a sensor or context hub, or
 * other auxiliary processing units.
 *
 * For more information, see the NDK documentation for <code>AHardwareBuffer</code>.
 */
public final class HardwareBuffer implements Parcelable, AutoCloseable {
    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "RGB", "BLOB", "YCBCR_", "D_", "DS_", "S_" }, value = {
            RGBA_8888,
            RGBA_FP16,
            RGBA_1010102,
            RGBX_8888,
            RGB_888,
            RGB_565,
            BLOB,
            YCBCR_420_888,
            D_16,
            D_24,
            DS_24UI8,
            D_FP32,
            DS_FP32UI8,
            S_UI8,
            YCBCR_P010,
            YCBCR_P210,
            R_8,
            R_16,
            RG_1616,
            RGBA_10101010,
    })
    public @interface Format {
    }

    @Format
    /** Format: 8 bits each red, green, blue, alpha */
    public static final int RGBA_8888    = 1;
    /** Format: 8 bits each red, green, blue, alpha, alpha is always 0xFF */
    public static final int RGBX_8888    = 2;
    /** Format: 8 bits each red, green, blue, no alpha */
    public static final int RGB_888      = 3;
    /** Format: 5 bits each red and blue, 6 bits green, no alpha */
    public static final int RGB_565      = 4;
    /** Format: 16 bits each red, green, blue, alpha */
    public static final int RGBA_FP16    = 0x16;
    /** Format: 10 bits each red, green, blue, 2 bits alpha */
    public static final int RGBA_1010102 = 0x2b;
    /** Format: opaque format used for raw data transfer; must have a height of 1 */
    public static final int BLOB         = 0x21;
    /** Format: Planar YCbCr 420; must have an even width and height */
    public static final int YCBCR_420_888 = 0x23;
    /** Format: 16 bits depth */
    public static final int D_16         = 0x30;
    /** Format: 24 bits depth */
    public static final int D_24         = 0x31;
    /** Format: 24 bits depth, 8 bits stencil */
    public static final int DS_24UI8     = 0x32;
    /** Format: 32 bits depth */
    public static final int D_FP32       = 0x33;
    /** Format: 32 bits depth, 8 bits stencil */
    public static final int DS_FP32UI8   = 0x34;
    /** Format: 8 bits stencil */
    public static final int S_UI8        = 0x35;
    /**
     * <p>Android YUV P010 format.</p>
     *
     * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
     * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
     * little-endian value, with the lower 6 bits set to zero.
     */
    public static final int YCBCR_P010    = 0x36;
    /**
     * <p>Android YUV P210 format.</p>
     *
     * P210 is a 4:2:2 YCbCr semiplanar format comprised of a WxH Y plane
     * followed by a WxH CbCr plane. Each sample is represented by a 16-bit
     * little-endian value, with the lower 6 bits set to zero.
     */
    @FlaggedApi(android.media.codec.Flags.FLAG_P210_FORMAT_SUPPORT)
    public static final int YCBCR_P210    = 0x3c;

    /** Format: 8 bits red */
    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
    public static final int R_8           = 0x38;
    /**
     * Format: 16 bits red. When sampled on the GPU this is represented as an
     * unsigned integer instead of implicit unsigned normalize.
     * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
     */
    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
    public static final int R_16          = 0x39;
    /**
     * Format: 16 bits each red, green. When sampled on the GPU this is represented
     * as an unsigned integer instead of implicit unsigned normalize.
     * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
     */
    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
    public static final int RG_1616       = 0x3a;
    /** Format: 10 bits each red, green, blue, alpha */
    @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
    public static final int RGBA_10101010 = 0x3b;

    // Note: do not rename, this field is used by native code
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private long mNativeObject;

    // Invoked on destruction
    private Runnable mCleaner;

    private final CloseGuard mCloseGuard = CloseGuard.get();

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @LongDef(flag = true, value = {USAGE_CPU_READ_RARELY, USAGE_CPU_READ_OFTEN,
            USAGE_CPU_WRITE_RARELY, USAGE_CPU_WRITE_OFTEN, USAGE_GPU_SAMPLED_IMAGE,
            USAGE_GPU_COLOR_OUTPUT, USAGE_COMPOSER_OVERLAY, USAGE_PROTECTED_CONTENT,
            USAGE_VIDEO_ENCODE, USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA,
            USAGE_GPU_CUBE_MAP, USAGE_GPU_MIPMAP_COMPLETE, USAGE_FRONT_BUFFER})
    public @interface Usage {};

    @Usage
    /** Usage: The buffer will sometimes be read by the CPU */
    public static final long USAGE_CPU_READ_RARELY       = 2;
    /** Usage: The buffer will often be read by the CPU */
    public static final long USAGE_CPU_READ_OFTEN        = 3;

    /** Usage: The buffer will sometimes be written to by the CPU */
    public static final long USAGE_CPU_WRITE_RARELY      = 2 << 4;
    /** Usage: The buffer will often be written to by the CPU */
    public static final long USAGE_CPU_WRITE_OFTEN       = 3 << 4;

    /** Usage: The buffer will be read from by the GPU */
    public static final long USAGE_GPU_SAMPLED_IMAGE      = 1 << 8;
    /** Usage: The buffer will be written to by the GPU */
    public static final long USAGE_GPU_COLOR_OUTPUT       = 1 << 9;
    /**
     * The buffer will be used as a hardware composer overlay layer. That is, it will be displayed
     * using the system compositor via {@link SurfaceControl}
     *
     * This flag is currently only needed when using
     * {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}
     * to set a buffer. In all other cases, the framework adds this flag
     * internally to buffers that could be presented in a composer overlay.
     */
    public static final long USAGE_COMPOSER_OVERLAY = 1 << 11;
    /** Usage: The buffer must not be used outside of a protected hardware path */
    public static final long USAGE_PROTECTED_CONTENT      = 1 << 14;
    /** Usage: The buffer will be read by a hardware video encoder */
    public static final long USAGE_VIDEO_ENCODE           = 1 << 16;
    /** Usage: The buffer will be used for sensor direct data */
    public static final long USAGE_SENSOR_DIRECT_DATA     = 1 << 23;
    /** Usage: The buffer will be used as a shader storage or uniform buffer object */
    public static final long USAGE_GPU_DATA_BUFFER        = 1 << 24;
    /** Usage: The buffer will be used as a cube map texture */
    public static final long USAGE_GPU_CUBE_MAP           = 1 << 25;
    /** Usage: The buffer contains a complete mipmap hierarchy */
    public static final long USAGE_GPU_MIPMAP_COMPLETE    = 1 << 26;
    /** Usage: The buffer is used for front-buffer rendering. When front-buffering rendering is
     * specified, different usages may adjust their behavior as a result. For example, when
     * used as USAGE_GPU_COLOR_OUTPUT the buffer will behave similar to a single-buffered window.
     * When used with USAGE_COMPOSER_OVERLAY, the system will try to prioritize the buffer
     * receiving an overlay plane & avoid caching it in intermediate composition buffers. */
    public static final long USAGE_FRONT_BUFFER           = 1L << 32;

    /**
     * Creates a new <code>HardwareBuffer</code> instance.
     *
     * <p>Calling this method will throw an <code>IllegalStateException</code> if
     * format is not a supported Format type.</p>
     *
     * @param width The width in pixels of the buffer
     * @param height The height in pixels of the buffer
     * @param format The @Format of each pixel
     * @param layers The number of layers in the buffer
     * @param usage The @Usage flags describing how the buffer will be used
     * @return A <code>HardwareBuffer</code> instance if successful, or throws an
     *     IllegalArgumentException if the dimensions passed are invalid (either zero, negative, or
     *     too large to allocate), if the format is not supported, if the requested number of layers
     *     is less than one or not supported, or if the passed usage flags are not a supported set.
     */
    @NonNull
    public static HardwareBuffer create(
            @IntRange(from = 1) int width, @IntRange(from = 1) int height,
            @Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
        if (width <= 0) {
            throw new IllegalArgumentException("Invalid width " + width);
        }
        if (height <= 0) {
            throw new IllegalArgumentException("Invalid height " + height);
        }
        if (layers <= 0) {
            throw new IllegalArgumentException("Invalid layer count " + layers);
        }
        if (format == BLOB && height != 1) {
            throw new IllegalArgumentException("Height must be 1 when using the BLOB format");
        }
        long nativeObject = nCreateHardwareBuffer(width, height, format, layers, usage);
        if (nativeObject == 0) {
            throw new IllegalArgumentException("Unable to create a HardwareBuffer, either the " +
                    "dimensions passed were too large, too many image layers were requested, " +
                    "or an invalid set of usage flags or invalid format was passed");
        }
        return new HardwareBuffer(nativeObject);
    }

    /**
     * Queries whether the given buffer description is supported by the system. If this returns
     * true, then the allocation may succeed until resource exhaustion occurs. If this returns
     * false then this combination will never succeed.
     *
     * @param width The width in pixels of the buffer
     * @param height The height in pixels of the buffer
     * @param format The @Format of each pixel
     * @param layers The number of layers in the buffer
     * @param usage The @Usage flags describing how the buffer will be used
     * @return True if the combination is supported, false otherwise.
     */
    public static boolean isSupported(@IntRange(from = 1) int width, @IntRange(from = 1) int height,
            @Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
        if (width <= 0) {
            throw new IllegalArgumentException("Invalid width " + width);
        }
        if (height <= 0) {
            throw new IllegalArgumentException("Invalid height " + height);
        }
        if (layers <= 0) {
            throw new IllegalArgumentException("Invalid layer count " + layers);
        }
        if (format == BLOB && height != 1) {
            throw new IllegalArgumentException("Height must be 1 when using the BLOB format");
        }
        return nIsSupported(width, height, format, layers, usage);
    }

    /**
     * @hide
     * Returns a <code>HardwareBuffer</code> instance from <code>GraphicBuffer</code>
     *
     * @param graphicBuffer A GraphicBuffer to be wrapped as HardwareBuffer
     * @return A <code>HardwareBuffer</code> instance.
     */
    @NonNull
    public static HardwareBuffer createFromGraphicBuffer(@NonNull GraphicBuffer graphicBuffer) {
        long nativeObject = nCreateFromGraphicBuffer(graphicBuffer);
        return new HardwareBuffer(nativeObject);
    }

    /**
     * Private use only. See {@link #create(int, int, int, int, long)}. May also be
     * called from JNI using an already allocated native <code>HardwareBuffer</code>.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private HardwareBuffer(long nativeObject) {
        mNativeObject = nativeObject;
        long bufferSize = nEstimateSize(nativeObject);
        ClassLoader loader = HardwareBuffer.class.getClassLoader();
        NativeAllocationRegistry registry = new NativeAllocationRegistry(
                loader, nGetNativeFinalizer(), bufferSize);
        mCleaner = registry.registerNativeAllocation(this, mNativeObject);
        mCloseGuard.open("HardwareBuffer.close");
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            mCloseGuard.warnIfOpen();
            close();
        } finally {
            super.finalize();
        }
    }

    /**
     * Returns the width of this buffer in pixels.
     */
    public int getWidth() {
        checkClosed("width");
        return nGetWidth(mNativeObject);
    }

    /**
     * Returns the height of this buffer in pixels.
     */
    public int getHeight() {
        checkClosed("height");
        return nGetHeight(mNativeObject);
    }

    /**
     * Returns the @Format of this buffer.
     */
    @Format
    public int getFormat() {
        checkClosed("format");
        return nGetFormat(mNativeObject);
    }

    /**
     * Returns the number of layers in this buffer.
     */
    public int getLayers() {
        checkClosed("layer count");
        return nGetLayers(mNativeObject);
    }

    /**
     * Returns the usage flags of the usage hints set on this buffer.
     */
    public long getUsage() {
        checkClosed("usage");
        return nGetUsage(mNativeObject);
    }

    /**
     * Returns the system-wide unique id for this buffer
     *
     * This can be useful as a cache key for associating additional objects with
     * a given HardwareBuffer, such as associating an imported EGLImage with
     * the target HardwareBuffer when processing a stream of buffers from
     * ImageReader.
     *
     * This can also be useful for doing cross-process buffer caching. As sending
     * a HardwareBuffer over Binder is slower than sending a long, this can be
     * used as reliable cache key after an initial handshake that passes the
     * HardwareBuffers themselves to later be referred to using only the id.
     */
    public long getId() {
        checkClosed("id");
        return nGetId(mNativeObject);
    }

    private void checkClosed(String name) {
        if (isClosed()) {
            throw new IllegalStateException("This HardwareBuffer has been closed and its "
                    + name + " cannot be obtained.");
        }
    }

    /**
     * Destroys this buffer immediately. Calling this method frees up any
     * underlying native resources. After calling this method, this buffer
     * must not be used in any way.
     *
     * @see #isClosed()
     */
    @Override
    public void close() {
        if (!isClosed()) {
            mCloseGuard.close();
            mNativeObject = 0;
            mCleaner.run();
            mCleaner = null;
        }
    }

    /**
     * Indicates whether this buffer has been closed. A closed buffer cannot
     * be used in any way: the buffer cannot be written to a parcel, etc.
     *
     * @return True if this <code>HardwareBuffer</code> is in a closed state,
     *         false otherwise.
     *
     * @see #close()
     */
    public boolean isClosed() {
        return mNativeObject == 0;
    }

    @Override
    public int describeContents() {
        return Parcelable.CONTENTS_FILE_DESCRIPTOR;
    }

    /**
     * Flatten this object in to a Parcel.
     *
     * <p>Calling this method will throw an <code>IllegalStateException</code> if
     * {@link #close()} has been previously called.</p>
     *
     * @param dest The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written.
     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        if (isClosed()) {
            throw new IllegalStateException("This HardwareBuffer has been closed and cannot be "
                    + "written to a parcel.");
        }
        nWriteHardwareBufferToParcel(mNativeObject, dest);
    }

    public static final @android.annotation.NonNull Parcelable.Creator<HardwareBuffer> CREATOR =
            new Parcelable.Creator<HardwareBuffer>() {
        public HardwareBuffer createFromParcel(Parcel in) {
            if (in == null) {
                throw new NullPointerException("null passed to createFromParcel");
            }
            long nativeObject = nReadHardwareBufferFromParcel(in);
            if (nativeObject != 0) {
                return new HardwareBuffer(nativeObject);
            }
            throw new BadParcelableException("Failed to read hardware buffer");
        }

        public HardwareBuffer[] newArray(int size) {
            return new HardwareBuffer[size];
        }
    };

    private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
            long usage);
    private static native long nCreateFromGraphicBuffer(GraphicBuffer graphicBuffer);
    private static native long nGetNativeFinalizer();
    private static native void nWriteHardwareBufferToParcel(long nativeObject, Parcel dest);
    private static native long nReadHardwareBufferFromParcel(Parcel in);
    @FastNative
    private static native int nGetWidth(long nativeObject);
    @FastNative
    private static native int nGetHeight(long nativeObject);
    @FastNative
    private static native int nGetFormat(long nativeObject);
    @FastNative
    private static native int nGetLayers(long nativeObject);
    @FastNative
    private static native long nGetUsage(long nativeObject);
    private static native boolean nIsSupported(int width, int height, int format, int layers,
            long usage);
    @CriticalNative
    private static native long nEstimateSize(long nativeObject);
    @CriticalNative
    private static native long nGetId(long nativeObject);
}
