/*
 * Copyright 2015 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_NDEBUG 0
#define LOG_TAG "SurfaceUtils"
#include <utils/Log.h>

#include <android/api-level.h>
#include <media/hardware/VideoAPI.h>
#include <media/stagefright/SurfaceUtils.h>
#include <gui/Surface.h>

extern "C" int android_get_application_target_sdk_version();

namespace android {

status_t setNativeWindowSizeFormatAndUsage(
        ANativeWindow *nativeWindow /* nonnull */,
        int width, int height, int format, int rotation, int usage, bool reconnect) {
    status_t err = NO_ERROR;

    // In some cases we need to reconnect so that we can dequeue all buffers
    if (reconnect) {
        err = nativeWindowDisconnect(nativeWindow, "setNativeWindowSizeFormatAndUsage");
        if (err != NO_ERROR) {
            ALOGE("nativeWindowDisconnect failed: %s (%d)", strerror(-err), -err);
            return err;
        }

        err = nativeWindowConnect(nativeWindow, "setNativeWindowSizeFormatAndUsage");
        if (err != NO_ERROR) {
            ALOGE("nativeWindowConnect failed: %s (%d)", strerror(-err), -err);
            return err;
        }
    }

    err = native_window_set_buffers_dimensions(nativeWindow, width, height);
    if (err != NO_ERROR) {
        ALOGE("native_window_set_buffers_dimensions failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    err = native_window_set_buffers_format(nativeWindow, format);
    if (err != NO_ERROR) {
        ALOGE("native_window_set_buffers_format failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    int transform = 0;
    if ((rotation % 90) == 0) {
        switch ((rotation / 90) & 3) {
            case 1:  transform = HAL_TRANSFORM_ROT_90;  break;
            case 2:  transform = HAL_TRANSFORM_ROT_180; break;
            case 3:  transform = HAL_TRANSFORM_ROT_270; break;
            default: transform = 0;                     break;
        }
    }

    err = native_window_set_buffers_transform(nativeWindow, transform);
    if (err != NO_ERROR) {
        ALOGE("native_window_set_buffers_transform failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    int consumerUsage = 0;
    err = nativeWindow->query(nativeWindow, NATIVE_WINDOW_CONSUMER_USAGE_BITS, &consumerUsage);
    if (err != NO_ERROR) {
        ALOGW("failed to get consumer usage bits. ignoring");
        err = NO_ERROR;
    }

    // Make sure to check whether either Stagefright or the video decoder
    // requested protected buffers.
    if (usage & GRALLOC_USAGE_PROTECTED) {
        // Check if the ANativeWindow sends images directly to SurfaceFlinger.
        int queuesToNativeWindow = 0;
        err = nativeWindow->query(
                nativeWindow, NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER, &queuesToNativeWindow);
        if (err != NO_ERROR) {
            ALOGE("error authenticating native window: %s (%d)", strerror(-err), -err);
            return err;
        }

        // Check if the consumer end of the ANativeWindow can handle protected content.
        int isConsumerProtected = 0;
        err = nativeWindow->query(
                nativeWindow, NATIVE_WINDOW_CONSUMER_IS_PROTECTED, &isConsumerProtected);
        if (err != NO_ERROR) {
            ALOGE("error query native window: %s (%d)", strerror(-err), -err);
            return err;
        }

        // Deny queuing into native window if neither condition is satisfied.
        if (queuesToNativeWindow != 1 && isConsumerProtected != 1) {
            ALOGE("native window cannot handle protected buffers: the consumer should either be "
                  "a hardware composer or support hardware protection");
            return PERMISSION_DENIED;
        }
    }

    uint64_t finalUsage = (uint32_t) usage | (uint32_t) consumerUsage;
    ALOGV("gralloc usage: %#x(producer) + %#x(consumer) = 0x%" PRIx64,
            usage, consumerUsage, finalUsage);
    err = native_window_set_usage(nativeWindow, finalUsage);
    if (err != NO_ERROR) {
        ALOGE("native_window_set_usage failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    err = native_window_set_scaling_mode(
            nativeWindow, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
    if (err != NO_ERROR) {
        ALOGE("native_window_set_scaling_mode failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    ALOGD("set up nativeWindow %p for %dx%d, color %#x, rotation %d, usage 0x%" PRIx64,
            nativeWindow, width, height, format, rotation, finalUsage);
    return NO_ERROR;
}

void setNativeWindowHdrMetadata(ANativeWindow *nativeWindow, HDRStaticInfo *info) {
    // If mastering max and min luminance fields are 0, do not use them.
    // It indicates the value may not be present in the stream.
    if ((float)info->sType1.mMaxDisplayLuminance > 0.0f &&
        (info->sType1.mMinDisplayLuminance * 0.0001f) > 0.0f) {
        struct android_smpte2086_metadata smpte2086_meta = {
                .displayPrimaryRed = {
                        info->sType1.mR.x * 0.00002f,
                        info->sType1.mR.y * 0.00002f
                },
                .displayPrimaryGreen = {
                        info->sType1.mG.x * 0.00002f,
                        info->sType1.mG.y * 0.00002f
                },
                .displayPrimaryBlue = {
                        info->sType1.mB.x * 0.00002f,
                        info->sType1.mB.y * 0.00002f
                },
                .whitePoint = {
                        info->sType1.mW.x * 0.00002f,
                        info->sType1.mW.y * 0.00002f
                },
                .maxLuminance = (float) info->sType1.mMaxDisplayLuminance,
                .minLuminance = info->sType1.mMinDisplayLuminance * 0.0001f
        };

        int err = native_window_set_buffers_smpte2086_metadata(nativeWindow, &smpte2086_meta);
        ALOGW_IF(err != 0, "failed to set smpte2086 metadata on surface (%d)", err);
    }

    // If the content light level fields are 0, do not use them, it
    // indicates the value may not be present in the stream.
    if ((float)info->sType1.mMaxContentLightLevel > 0.0f &&
        (float)info->sType1.mMaxFrameAverageLightLevel > 0.0f) {
        struct android_cta861_3_metadata cta861_meta = {
                .maxContentLightLevel = (float) info->sType1.mMaxContentLightLevel,
                .maxFrameAverageLightLevel = (float) info->sType1.mMaxFrameAverageLightLevel
        };

        int err = native_window_set_buffers_cta861_3_metadata(nativeWindow, &cta861_meta);
        ALOGW_IF(err != 0, "failed to set cta861_3 metadata on surface (%d)", err);
    }
}

status_t setNativeWindowRotation(
        ANativeWindow *nativeWindow /* nonnull */, int rotation) {

    int transform = 0;
    if ((rotation % 90) == 0) {
        switch ((rotation / 90) & 3) {
            case 1:  transform = HAL_TRANSFORM_ROT_90;  break;
            case 2:  transform = HAL_TRANSFORM_ROT_180; break;
            case 3:  transform = HAL_TRANSFORM_ROT_270; break;
            default: transform = 0;                     break;
        }
    }

    return native_window_set_buffers_transform(nativeWindow, transform);
}

status_t pushBlankBuffersToNativeWindow(ANativeWindow *nativeWindow /* nonnull */) {
    status_t err = NO_ERROR;
    ANativeWindowBuffer* anb = nullptr;
    int numBufs = 0;
    int minUndequeuedBufs = 0;

    auto handleError = [](ANativeWindow *nativeWindow, ANativeWindowBuffer* anb, status_t err)
    {
        if (anb != nullptr) {
            nativeWindow->cancelBuffer(nativeWindow, anb, -1);
            anb = nullptr;
        }

        // Clean up after success or error.
        status_t err2 = native_window_api_disconnect(nativeWindow, NATIVE_WINDOW_API_CPU);
        if (err2 != NO_ERROR) {
            ALOGE("error pushing blank frames: api_disconnect failed: %s (%d)",
                    strerror(-err2), -err2);
            if (err == NO_ERROR) {
                err = err2;
            }
        }

        err2 = nativeWindowConnect(nativeWindow, "pushBlankBuffersToNativeWindow(err2)");
        if (err2 != NO_ERROR) {
            ALOGE("error pushing blank frames: api_connect failed: %s (%d)", strerror(-err), -err);
            if (err == NO_ERROR) {
                err = err2;
            }
        }

        return err;
    };

    // We need to set sidebandStream to nullptr before pushing blank buffers
    err = native_window_set_sideband_stream(nativeWindow, nullptr);
    if (err != NO_ERROR) {
        ALOGE("error setting sidebandStream to nullptr: %s (%d)", strerror(-err), -err);
        return err;
    }

    // We need to reconnect to the ANativeWindow as a CPU client to ensure that
    // no frames get dropped by SurfaceFlinger assuming that these are video
    // frames.
    err = nativeWindowDisconnect(nativeWindow, "pushBlankBuffersToNativeWindow");
    if (err != NO_ERROR) {
        ALOGE("error pushing blank frames: api_disconnect failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    err = native_window_api_connect(nativeWindow, NATIVE_WINDOW_API_CPU);
    if (err != NO_ERROR) {
        ALOGE("error pushing blank frames: api_connect failed: %s (%d)", strerror(-err), -err);
        (void)nativeWindowConnect(nativeWindow, "pushBlankBuffersToNativeWindow(err)");
        return err;
    }

    err = setNativeWindowSizeFormatAndUsage(
            nativeWindow, 1, 1, HAL_PIXEL_FORMAT_RGBX_8888, 0, GRALLOC_USAGE_SW_WRITE_OFTEN,
            false /* reconnect */);
    if (err != NO_ERROR) {
        return handleError(nativeWindow, anb, err);
    }

    static_cast<Surface*>(nativeWindow)->getIGraphicBufferProducer()->allowAllocation(true);

    // In nonblocking mode(timetout = 0), native_window_dequeue_buffer_and_wait()
    // can fail with timeout. Changing to blocking mode will ensure that dequeue
    // does not timeout.
    static_cast<Surface*>(nativeWindow)->getIGraphicBufferProducer()->setDequeueTimeout(-1);

    err = nativeWindow->query(nativeWindow,
            NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBufs);
    if (err != NO_ERROR) {
        ALOGE("error pushing blank frames: MIN_UNDEQUEUED_BUFFERS query "
                "failed: %s (%d)", strerror(-err), -err);
        return handleError(nativeWindow, anb, err);
    }

    numBufs = minUndequeuedBufs + 1;
    err = native_window_set_buffer_count(nativeWindow, numBufs);
    if (err != NO_ERROR) {
        ALOGE("error pushing blank frames: set_buffer_count failed: %s (%d)", strerror(-err), -err);
        return handleError(nativeWindow, anb, err);
    }

    // We push numBufs + 1 buffers to ensure that we've drawn into the same
    // buffer twice.  This should guarantee that the buffer has been displayed
    // on the screen and then been replaced, so an previous video frames are
    // guaranteed NOT to be currently displayed.
    for (int i = 0; i < numBufs + 1; i++) {
        err = native_window_dequeue_buffer_and_wait(nativeWindow, &anb);
        if (err != NO_ERROR) {
            ALOGE("error pushing blank frames: dequeueBuffer failed: %s (%d)",
                    strerror(-err), -err);
            break;
        }

        sp<GraphicBuffer> buf(GraphicBuffer::from(anb));

        // Fill the buffer with the a 1x1 checkerboard pattern ;)
        uint32_t *img = nullptr;
        err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
        if (err != NO_ERROR) {
            ALOGE("error pushing blank frames: lock failed: %s (%d)", strerror(-err), -err);
            break;
        }
        if (img == nullptr) {
            (void)buf->unlock(); // Since lock() was successful.
            ALOGE("error pushing blank frames: lock succeeded: buf mapping is nullptr");
            break;
        }

        *img = 0;

        err = buf->unlock();
        if (err != NO_ERROR) {
            ALOGE("error pushing blank frames: unlock failed: %s (%d)", strerror(-err), -err);
            break;
        }

        err = nativeWindow->queueBuffer(nativeWindow, buf->getNativeBuffer(), -1);
        if (err != NO_ERROR) {
            ALOGE("error pushing blank frames: queueBuffer failed: %s (%d)", strerror(-err), -err);
            break;
        }

        anb = nullptr;
    }

    return handleError(nativeWindow, anb, err);
}

status_t nativeWindowConnect(ANativeWindow *surface, const char *reason) {
    ALOGD("connecting to surface %p, reason %s", surface, reason);

    status_t err = native_window_api_connect(surface, NATIVE_WINDOW_API_MEDIA);
    ALOGE_IF(err != OK, "Failed to connect to surface %p, err %d", surface, err);

    return err;
}

status_t surfaceConnectWithListener(
        const sp<Surface> &surface, sp<SurfaceListener> listener, const char *reason) {
    ALOGD("connecting to surface %p, reason %s", surface.get(), reason);

    status_t err = surface->connect(NATIVE_WINDOW_API_MEDIA, listener);
    ALOGE_IF(err != OK, "Failed to connect from surface %p, err %d", surface.get(), err);

    return err;
}

status_t nativeWindowDisconnect(ANativeWindow *surface, const char *reason) {
    ALOGD("disconnecting from surface %p, reason %s", surface, reason);

    status_t err = native_window_api_disconnect(surface, NATIVE_WINDOW_API_MEDIA);
    ALOGE_IF(err != OK, "Failed to disconnect from surface %p, err %d", surface, err);

    return err;
}

status_t disableLegacyBufferDropPostQ(const sp<Surface> &surface) {
    sp<IGraphicBufferProducer> igbp =
            surface ? surface->getIGraphicBufferProducer() : nullptr;
    if (igbp) {
        int targetSdk = android_get_application_target_sdk_version();
        // When the caller is not an app (e.g. MediaPlayer in mediaserver)
        // targetSdk is __ANDROID_API_FUTURE__.
        bool drop =
                targetSdk < __ANDROID_API_Q__ ||
                targetSdk == __ANDROID_API_FUTURE__;
        if (!drop) {
            status_t err = igbp->setLegacyBufferDrop(false);
            if (err == NO_ERROR) {
                ALOGD("legacy buffer drop disabled: target sdk (%d)",
                      targetSdk);
            } else {
                ALOGD("disabling legacy buffer drop failed: %d", err);
            }
        }
    }
    return NO_ERROR;
}
}  // namespace android

