/*
 * Copyright 2016 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 ATRACE_TAG ATRACE_TAG_GRAPHICS

#include "gpuservice/GpuService.h"

#include <android-base/stringprintf.h>
#include <android-base/properties.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/Parcel.h>
#include <binder/PermissionCache.h>
#include <cutils/properties.h>
#include <gpumem/GpuMem.h>
#include <gpuwork/GpuWork.h>
#include <gpustats/GpuStats.h>
#include <private/android_filesystem_config.h>
#include <tracing/GpuMemTracer.h>
#include <utils/String8.h>
#include <utils/Trace.h>
#include <vkjson.h>
#include <vkprofiles.h>

#include <thread>
#include <memory>

namespace android {

using base::StringAppendF;

namespace {
status_t cmdHelp(int out);
status_t cmdVkjson(int out, int err);
status_t cmdVkprofiles(int out, int err);
void dumpGameDriverInfo(std::string* result);
} // namespace

const String16 sDump("android.permission.DUMP");
const String16 sAccessGpuServicePermission("android.permission.ACCESS_GPU_SERVICE");
const std::string sAngleGlesDriverSuffix = "angle";

const char* const GpuService::SERVICE_NAME = "gpu";

GpuService::GpuService()
      : mGpuMem(std::make_shared<GpuMem>()),
        mGpuWork(std::make_shared<gpuwork::GpuWork>()),
        mGpuStats(std::make_unique<GpuStats>()),
        mGpuMemTracer(std::make_unique<GpuMemTracer>()) {

    mGpuMemAsyncInitThread = std::make_unique<std::thread>([this] (){
        mGpuMem->initialize();
        mGpuMemTracer->initialize(mGpuMem);
    });

    mGpuWorkAsyncInitThread = std::make_unique<std::thread>([this]() {
        mGpuWork->initialize();
    });
};

GpuService::~GpuService() {
    mGpuMem->stop();
    mGpuWork->stop();

    mGpuWorkAsyncInitThread->join();
    mGpuMemAsyncInitThread->join();
}

void GpuService::setGpuStats(const std::string& driverPackageName,
                             const std::string& driverVersionName, uint64_t driverVersionCode,
                             int64_t driverBuildTime, const std::string& appPackageName,
                             const int32_t vulkanVersion, GpuStatsInfo::Driver driver,
                             bool isDriverLoaded, int64_t driverLoadingTime) {
    mGpuStats->insertDriverStats(driverPackageName, driverVersionName, driverVersionCode,
                                 driverBuildTime, appPackageName, vulkanVersion, driver,
                                 isDriverLoaded, driverLoadingTime);
}

void GpuService::setTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode,
                                const GpuStatsInfo::Stats stats, const uint64_t value) {
    mGpuStats->insertTargetStats(appPackageName, driverVersionCode, stats, value);
}

void GpuService::setTargetStatsArray(const std::string& appPackageName,
                                const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats,
                                const uint64_t* values, const uint32_t valueCount) {
    mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount);
}

void GpuService::addVulkanEngineName(const std::string& appPackageName,
                                     const uint64_t driverVersionCode,
                                     const char* engineName) {
    mGpuStats->addVulkanEngineName(appPackageName, driverVersionCode, engineName);
}

void GpuService::toggleAngleAsSystemDriver(bool enabled) {
    IPCThreadState* ipc = IPCThreadState::self();
    const int pid = ipc->getCallingPid();
    const int uid = ipc->getCallingUid();

    // only system_server with the ACCESS_GPU_SERVICE permission is allowed to set
    // persist.graphics.egl
    if (uid != AID_SYSTEM ||
        !PermissionCache::checkPermission(sAccessGpuServicePermission, pid, uid)) {
        ALOGE("Permission Denial: can't set persist.graphics.egl from setAngleAsSystemDriver() "
                "pid=%d, uid=%d\n", pid, uid);
        return;
    }

    std::lock_guard<std::mutex> lock(mLock);
    if (enabled) {
        android::base::SetProperty("persist.graphics.egl", sAngleGlesDriverSuffix);
    } else {
        android::base::SetProperty("persist.graphics.egl", "");
    }
}


void GpuService::setUpdatableDriverPath(const std::string& driverPath) {
    IPCThreadState* ipc = IPCThreadState::self();
    const int pid = ipc->getCallingPid();
    const int uid = ipc->getCallingUid();

    // only system_server is allowed to set updatable driver path
    if (uid != AID_SYSTEM) {
        ALOGE("Permission Denial: can't set updatable driver path from pid=%d, uid=%d\n", pid, uid);
        return;
    }

    std::lock_guard<std::mutex> lock(mLock);
    mDeveloperDriverPath = driverPath;
}

std::string GpuService::getUpdatableDriverPath() {
    std::lock_guard<std::mutex> lock(mLock);
    return mDeveloperDriverPath;
}

status_t GpuService::shellCommand(int /*in*/, int out, int err, std::vector<String16>& args) {
    ATRACE_CALL();

    ALOGV("shellCommand");
    for (size_t i = 0, n = args.size(); i < n; i++)
        ALOGV("  arg[%zu]: '%s'", i, String8(args[i]).c_str());

    if (args.size() >= 1) {
        if (args[0] == String16("vkjson")) return cmdVkjson(out, err);
        if (args[0] == String16("vkprofiles")) return cmdVkprofiles(out, err);
        if (args[0] == String16("help")) return cmdHelp(out);
    }
    // no command, or unrecognized command
    cmdHelp(err);
    return BAD_VALUE;
}

status_t GpuService::doDump(int fd, const Vector<String16>& args, bool /*asProto*/) {
    std::string result;

    IPCThreadState* ipc = IPCThreadState::self();
    const int pid = ipc->getCallingPid();
    const int uid = ipc->getCallingUid();

    if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) {
        StringAppendF(&result, "Permission Denial: can't dump gpu from pid=%d, uid=%d\n", pid, uid);
    } else {
        bool dumpAll = true;
        bool dumpDriverInfo = false;
        bool dumpMem = false;
        bool dumpStats = false;
        bool dumpWork = false;
        size_t numArgs = args.size();

        if (numArgs) {
            for (size_t index = 0; index < numArgs; ++index) {
                if (args[index] == String16("--gpustats")) {
                    dumpStats = true;
                } else if (args[index] == String16("--gpudriverinfo")) {
                    dumpDriverInfo = true;
                } else if (args[index] == String16("--gpumem")) {
                    dumpMem = true;
                } else if (args[index] == String16("--gpuwork")) {
                    dumpWork = true;
                }
            }
            dumpAll = !(dumpDriverInfo || dumpMem || dumpStats || dumpWork);
        }

        if (dumpAll || dumpDriverInfo) {
            dumpGameDriverInfo(&result);
            result.append("\n");
        }
        if (dumpAll || dumpMem) {
            mGpuMem->dump(args, &result);
            result.append("\n");
        }
        if (dumpAll || dumpStats) {
            mGpuStats->dump(args, &result);
            result.append("\n");
        }
         if (dumpAll || dumpWork) {
            mGpuWork->dump(args, &result);
            result.append("\n");
        }
    }

    write(fd, result.c_str(), result.size());
    return NO_ERROR;
}

namespace {

status_t cmdHelp(int out) {
    FILE* outs = fdopen(out, "w");
    if (!outs) {
        ALOGE("gpuservice: failed to create out stream: %s (%d)", strerror(errno), errno);
        return BAD_VALUE;
    }
    fprintf(outs,
            "GPU Service commands:\n"
            "  vkjson      dump Vulkan properties as JSON\n"
            "  vkprofiles  print support for select Vulkan profiles\n");
    fclose(outs);
    return NO_ERROR;
}

status_t cmdVkjson(int out, int /*err*/) {
    dprintf(out, "%s\n", VkJsonInstanceToJson(VkJsonGetInstance()).c_str());
    return NO_ERROR;
}

status_t cmdVkprofiles(int out, int /*err*/) {
    dprintf(out, "%s\n", android::vkprofiles::vkProfiles().c_str());
    return NO_ERROR;
}

void dumpGameDriverInfo(std::string* result) {
    if (!result) return;

    char stableGameDriver[PROPERTY_VALUE_MAX] = {};
    property_get("ro.gfx.driver.0", stableGameDriver, "unsupported");
    StringAppendF(result, "Stable Game Driver: %s\n", stableGameDriver);

    char preReleaseGameDriver[PROPERTY_VALUE_MAX] = {};
    property_get("ro.gfx.driver.1", preReleaseGameDriver, "unsupported");
    StringAppendF(result, "Pre-release Game Driver: %s\n", preReleaseGameDriver);
}

} // anonymous namespace

} // namespace android
