/*
**
** Copyright 2009, 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 "GraphicBufferAllocator"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS

#include <ui/GraphicBufferAllocator.h>

#include <limits.h>
#include <stdio.h>

#include <grallocusage/GrallocUsageConversion.h>

#include <android-base/stringprintf.h>
#include <log/log.h>
#include <utils/Singleton.h>
#include <utils/Trace.h>

#include <ui/Gralloc.h>
#include <ui/Gralloc2.h>
#include <ui/Gralloc3.h>
#include <ui/Gralloc4.h>
#include <ui/Gralloc5.h>
#include <ui/GraphicBufferMapper.h>

namespace android {
// ---------------------------------------------------------------------------

using base::StringAppendF;

ANDROID_SINGLETON_STATIC_INSTANCE( GraphicBufferAllocator )

Mutex GraphicBufferAllocator::sLock;
KeyedVector<buffer_handle_t,
    GraphicBufferAllocator::alloc_rec_t> GraphicBufferAllocator::sAllocList;

GraphicBufferAllocator::GraphicBufferAllocator() : mMapper(GraphicBufferMapper::getInstance()) {
    switch (mMapper.getMapperVersion()) {
        case GraphicBufferMapper::GRALLOC_5:
            mAllocator = std::make_unique<const Gralloc5Allocator>(
                    reinterpret_cast<const Gralloc5Mapper&>(mMapper.getGrallocMapper()));
            break;
        case GraphicBufferMapper::GRALLOC_4:
            mAllocator = std::make_unique<const Gralloc4Allocator>(
                    reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper()));
            break;
        case GraphicBufferMapper::GRALLOC_3:
            mAllocator = std::make_unique<const Gralloc3Allocator>(
                    reinterpret_cast<const Gralloc3Mapper&>(mMapper.getGrallocMapper()));
            break;
        case GraphicBufferMapper::GRALLOC_2:
            mAllocator = std::make_unique<const Gralloc2Allocator>(
                    reinterpret_cast<const Gralloc2Mapper&>(mMapper.getGrallocMapper()));
            break;
    }
    LOG_ALWAYS_FATAL_IF(!mAllocator->isLoaded(),
                        "Failed to load matching allocator for mapper version %d",
                        mMapper.getMapperVersion());
}

GraphicBufferAllocator::~GraphicBufferAllocator() {}

uint64_t GraphicBufferAllocator::getTotalSize() const {
    Mutex::Autolock _l(sLock);
    uint64_t total = 0;
    for (size_t i = 0; i < sAllocList.size(); ++i) {
        total += sAllocList.valueAt(i).size;
    }
    return total;
}

void GraphicBufferAllocator::dump(std::string& result, bool less) const {
    Mutex::Autolock _l(sLock);
    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
    uint64_t total = 0;
    result.append("GraphicBufferAllocator buffers:\n");
    const size_t count = list.size();
    StringAppendF(&result, "%14s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
                  "W (Stride) x H", "Layers", "Format", "Usage", "Requestor");
    for (size_t i = 0; i < count; i++) {
        const alloc_rec_t& rec(list.valueAt(i));
        std::string sizeStr = (rec.size)
                ? base::StringPrintf("%7.2f KiB", static_cast<double>(rec.size) / 1024.0)
                : "unknown";
        StringAppendF(&result, "%14p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
                      list.keyAt(i), sizeStr.c_str(), rec.width, rec.stride, rec.height,
                      rec.layerCount, rec.format, rec.usage, rec.requestorName.c_str());
        total += rec.size;
    }
    StringAppendF(&result, "Total allocated by GraphicBufferAllocator (estimate): %.2f KB\n",
                  static_cast<double>(total) / 1024.0);

    result.append(mAllocator->dumpDebugInfo(less));
}

void GraphicBufferAllocator::dumpToSystemLog(bool less) {
    std::string s;
    GraphicBufferAllocator::getInstance().dump(s, less);
    ALOGD("%s", s.c_str());
}

auto GraphicBufferAllocator::allocate(const AllocationRequest& request) -> AllocationResult {
    ATRACE_CALL();
    if (!request.width || !request.height) {
        return AllocationResult(BAD_VALUE);
    }

    const auto width = request.width;
    const auto height = request.height;

    const uint32_t bpp = bytesPerPixel(request.format);
    if (std::numeric_limits<size_t>::max() / width / height < static_cast<size_t>(bpp)) {
        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
              "usage %" PRIx64 ": Requesting too large a buffer size",
              request.width, request.height, request.layerCount, request.format, request.usage);
        return AllocationResult(BAD_VALUE);
    }

    if (request.layerCount < 1) {
        return AllocationResult(BAD_VALUE);
    }

    auto result = mAllocator->allocate(request);
    if (result.status == UNKNOWN_TRANSACTION) {
        if (!request.extras.empty()) {
            ALOGE("Failed to allocate with additional options, allocator version mis-match? "
                  "gralloc version = %d",
                  (int)mMapper.getMapperVersion());
            return result;
        }
        // If there's no additional options, fall back to previous allocate version
        result.status = mAllocator->allocate(request.requestorName, request.width, request.height,
                                             request.format, request.layerCount, request.usage,
                                             &result.stride, &result.handle, request.importBuffer);
    }

    if (result.status != NO_ERROR) {
        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
              "usage %" PRIx64 ": %d",
              request.width, request.height, request.layerCount, request.format, request.usage,
              result.status);
        return result;
    }

    if (!request.importBuffer) {
        return result;
    }
    size_t bufSize;

    // if stride has no meaning or is too large,
    // approximate size with the input width instead
    if ((result.stride) != 0 &&
        std::numeric_limits<size_t>::max() / height / (result.stride) < static_cast<size_t>(bpp)) {
        bufSize = static_cast<size_t>(width) * height * bpp;
    } else {
        bufSize = static_cast<size_t>((result.stride)) * height * bpp;
    }

    Mutex::Autolock _l(sLock);
    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
    alloc_rec_t rec;
    rec.width = width;
    rec.height = height;
    rec.stride = result.stride;
    rec.format = request.format;
    rec.layerCount = request.layerCount;
    rec.usage = request.usage;
    rec.size = bufSize;
    rec.requestorName = request.requestorName;
    list.add(result.handle, rec);

    return result;
}

status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, PixelFormat format,
                                                uint32_t layerCount, uint64_t usage,
                                                buffer_handle_t* handle, uint32_t* stride,
                                                std::string requestorName, bool importBuffer) {
    ATRACE_CALL();

    // make sure to not allocate a N x 0 or 0 x N buffer, since this is
    // allowed from an API stand-point allocate a 1x1 buffer instead.
    if (!width || !height)
        width = height = 1;

    const uint32_t bpp = bytesPerPixel(format);
    if (std::numeric_limits<size_t>::max() / width / height < static_cast<size_t>(bpp)) {
        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
              "usage %" PRIx64 ": Requesting too large a buffer size",
              width, height, layerCount, format, usage);
        return BAD_VALUE;
    }

    // Ensure that layerCount is valid.
    if (layerCount < 1) {
        layerCount = 1;
    }

    // TODO(b/72323293, b/72703005): Remove these invalid bits from callers
    usage &= ~static_cast<uint64_t>((1 << 10) | (1 << 13));

    status_t error = mAllocator->allocate(requestorName, width, height, format, layerCount, usage,
                                          stride, handle, importBuffer);
    if (error != NO_ERROR) {
        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
              "usage %" PRIx64 ": %d",
              width, height, layerCount, format, usage, error);
        return error;
    }

    if (!importBuffer) {
        return NO_ERROR;
    }
    size_t bufSize;

    // if stride has no meaning or is too large,
    // approximate size with the input width instead
    if ((*stride) != 0 &&
        std::numeric_limits<size_t>::max() / height / (*stride) < static_cast<size_t>(bpp)) {
        bufSize = static_cast<size_t>(width) * height * bpp;
    } else {
        bufSize = static_cast<size_t>((*stride)) * height * bpp;
    }

    Mutex::Autolock _l(sLock);
    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
    alloc_rec_t rec;
    rec.width = width;
    rec.height = height;
    rec.stride = *stride;
    rec.format = format;
    rec.layerCount = layerCount;
    rec.usage = usage;
    rec.size = bufSize;
    rec.requestorName = std::move(requestorName);
    list.add(*handle, rec);

    return NO_ERROR;
}
status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height, PixelFormat format,
                                          uint32_t layerCount, uint64_t usage,
                                          buffer_handle_t* handle, uint32_t* stride,
                                          std::string requestorName) {
    return allocateHelper(width, height, format, layerCount, usage, handle, stride, requestorName,
                          true);
}

status_t GraphicBufferAllocator::allocateRawHandle(uint32_t width, uint32_t height,
                                                   PixelFormat format, uint32_t layerCount,
                                                   uint64_t usage, buffer_handle_t* handle,
                                                   uint32_t* stride, std::string requestorName) {
    return allocateHelper(width, height, format, layerCount, usage, handle, stride, requestorName,
                          false);
}

// DEPRECATED
status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height, PixelFormat format,
                                          uint32_t layerCount, uint64_t usage,
                                          buffer_handle_t* handle, uint32_t* stride,
                                          uint64_t /*graphicBufferId*/, std::string requestorName) {
    return allocateHelper(width, height, format, layerCount, usage, handle, stride, requestorName,
                          true);
}

status_t GraphicBufferAllocator::free(buffer_handle_t handle)
{
    ATRACE_CALL();

    // We allocated a buffer from the allocator and imported it into the
    // mapper to get the handle.  We just need to free the handle now.
    mMapper.freeBuffer(handle);

    Mutex::Autolock _l(sLock);
    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
    list.removeItem(handle);

    return NO_ERROR;
}

bool GraphicBufferAllocator::supportsAdditionalOptions() const {
    return mAllocator->supportsAdditionalOptions();
}

// ---------------------------------------------------------------------------
}; // namespace android
