/*
 * Copyright (C) 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 LOG_NDEBUG 0
#define LOG_TAG "android_os_HwBinder"
#include <android-base/logging.h>

#include "android_os_HwBinder.h"

#include "android_os_HwParcel.h"
#include "android_os_HwRemoteBinder.h"

#include <cstring>

#include <nativehelper/JNIHelp.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <android/hidl/base/1.0/IBase.h>
#include <android/hidl/base/1.0/BpHwBase.h>
#include <android_runtime/AndroidRuntime.h>
#include <hidl/ServiceManagement.h>
#include <hidl/Status.h>
#include <hidl/HidlTransportSupport.h>
#include <hwbinder/IPCThreadState.h>
#include <hwbinder/ProcessState.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedUtfChars.h>
#include <vintf/parse_string.h>
#include <utils/misc.h>

#include "core_jni_helpers.h"

using android::AndroidRuntime;
using android::hardware::hidl_vec;
using android::hardware::hidl_string;
using android::hardware::IPCThreadState;
using android::hardware::ProcessState;
template<typename T>
using Return = android::hardware::Return<T>;

#define PACKAGE_PATH    "android/os"
#define CLASS_NAME      "HwBinder"
#define CLASS_PATH      PACKAGE_PATH "/" CLASS_NAME

namespace android {

static jclass gErrorClass;

static struct fields_t {
    jfieldID contextID;
    jmethodID onTransactID;
} gFields;

struct JHwBinderHolder : public RefBase {
    JHwBinderHolder() {}

    sp<JHwBinder> get(JNIEnv *env, jobject obj) {
        Mutex::Autolock autoLock(mLock);

        sp<JHwBinder> binder = mBinder.promote();

        if (binder == NULL) {
            binder = new JHwBinder(env, obj);
            mBinder = binder;
        }

        return binder;
    }

private:
    Mutex mLock;
    wp<JHwBinder> mBinder;

    DISALLOW_COPY_AND_ASSIGN(JHwBinderHolder);
};

// static
void JHwBinder::InitClass(JNIEnv *env) {
    ScopedLocalRef<jclass> clazz(
            env, FindClassOrDie(env, CLASS_PATH));

    gFields.contextID =
        GetFieldIDOrDie(env, clazz.get(), "mNativeContext", "J");

    gFields.onTransactID =
        GetMethodIDOrDie(
                env,
                clazz.get(),
                "onTransact",
                "(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V");
}

// static
sp<JHwBinderHolder> JHwBinder::SetNativeContext(
        JNIEnv *env, jobject thiz, const sp<JHwBinderHolder> &context) {
    sp<JHwBinderHolder> old =
        (JHwBinderHolder *)env->GetLongField(thiz, gFields.contextID);

    if (context != NULL) {
        context->incStrong(NULL /* id */);
    }

    if (old != NULL) {
        old->decStrong(NULL /* id */);
    }

    env->SetLongField(thiz, gFields.contextID, (long)context.get());

    return old;
}

// static
sp<JHwBinder> JHwBinder::GetNativeBinder(
        JNIEnv *env, jobject thiz) {
    JHwBinderHolder *holder =
        reinterpret_cast<JHwBinderHolder *>(
                env->GetLongField(thiz, gFields.contextID));

    return holder->get(env, thiz);
}

JHwBinder::JHwBinder(JNIEnv *env, jobject thiz) {
    jclass clazz = env->GetObjectClass(thiz);
    CHECK(clazz != NULL);

    mObject = env->NewGlobalRef(thiz);
}

JHwBinder::~JHwBinder() {
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    env->DeleteGlobalRef(mObject);
    mObject = NULL;
}

status_t JHwBinder::onTransact(
        uint32_t code,
        const hardware::Parcel &data,
        hardware::Parcel *reply,
        uint32_t flags,
        TransactCallback callback) {
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    bool isOneway = (flags & TF_ONE_WAY) != 0;
    ScopedLocalRef<jobject> replyObj(env, nullptr);
    sp<JHwParcel> replyContext = nullptr;

    ScopedLocalRef<jobject> requestObj(env, JHwParcel::NewObject(env));
    JHwParcel::GetNativeContext(env, requestObj.get())->setParcel(
            const_cast<hardware::Parcel *>(&data), false /* assumeOwnership */);


    if (!isOneway) {
        replyObj.reset(JHwParcel::NewObject(env));

        replyContext = JHwParcel::GetNativeContext(env, replyObj.get());

        replyContext->setParcel(reply, false /* assumeOwnership */);
        replyContext->setTransactCallback(callback);
    }

    env->CallVoidMethod(
            mObject,
            gFields.onTransactID,
            code,
            requestObj.get(),
            replyObj.get(),
            flags);

    if (env->ExceptionCheck()) {
        jthrowable excep = env->ExceptionOccurred();
        env->ExceptionDescribe();
        env->ExceptionClear();

        // It is illegal to call IsInstanceOf if there is a pending exception.
        // Attempting to do so results in a JniAbort which crashes the entire process.
        if (env->IsInstanceOf(excep, gErrorClass)) {
            /* It's an error */
            LOG(ERROR) << "Forcefully exiting";
            exit(1);
        } else {
            LOG(ERROR) << "Uncaught exception!";
        }

        env->DeleteLocalRef(excep);
    }

    status_t err = OK;

    if (!isOneway) {
        if (!replyContext->wasSent()) {
            // The implementation never finished the transaction.
            err = UNKNOWN_ERROR;  // XXX special error code instead?

            reply->setDataPosition(0 /* pos */);
        }

        // Release all temporary storage now that scatter-gather data
        // has been consolidated, either by calling the TransactCallback,
        // if wasSent() == true or clearing the reply parcel (setDataOffset above).
        replyContext->getStorage()->release(env);

        // We cannot permanently pass ownership of "data" and "reply" over to their
        // Java object wrappers (we don't own them ourselves).
        replyContext->setParcel(
                NULL /* parcel */, false /* assumeOwnership */);

    }

    JHwParcel::GetNativeContext(env, requestObj.get())->setParcel(
            NULL /* parcel */, false /* assumeOwnership */);

    return err;
}

}  // namespace android

////////////////////////////////////////////////////////////////////////////////

using namespace android;

static void releaseNativeContext(void *nativeContext) {
    sp<JHwBinderHolder> context = static_cast<JHwBinderHolder *>(nativeContext);

    if (context != NULL) {
        context->decStrong(NULL /* id */);
    }
}

static jlong JHwBinder_native_init(JNIEnv *env) {
    JHwBinder::InitClass(env);

    return reinterpret_cast<jlong>(&releaseNativeContext);
}

static void JHwBinder_native_setup(JNIEnv *env, jobject thiz) {
    sp<JHwBinderHolder> context = new JHwBinderHolder;
    JHwBinder::SetNativeContext(env, thiz, context);
}

static void JHwBinder_native_transact(
        JNIEnv * /* env */,
        jobject /* thiz */,
        jint /* code */,
        jobject /* requestObj */,
        jobject /* replyObj */,
        jint /* flags */) {
    CHECK(!"Should not be here");
}

static void JHwBinder_native_registerService(
        JNIEnv *env,
        jobject thiz,
        jstring serviceNameObj) {
    ScopedUtfChars str(env, serviceNameObj);
    if (str.c_str() == nullptr) {
        return;  // NPE will be pending.
    }

    sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz);

    /* TODO(b/33440494) this is not right */
    sp<hidl::base::V1_0::IBase> base = new hidl::base::V1_0::BpHwBase(binder);

    auto manager = hardware::defaultServiceManager();

    if (manager == nullptr) {
        LOG(ERROR) << "Could not get hwservicemanager.";
        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
        return;
    }

    Return<bool> ret = manager->add(str.c_str(), base);

    bool ok = ret.isOk() && ret;

    if (ok) {
        LOG(INFO) << "HwBinder: Starting thread pool for " << str.c_str();
        ::android::hardware::ProcessState::self()->startThreadPool();
    }

    signalExceptionForError(env, (ok ? OK : UNKNOWN_ERROR), true /* canThrowRemoteException */);
}

static jobject JHwBinder_native_getService(
        JNIEnv *env,
        jclass /* clazzObj */,
        jstring ifaceNameObj,
        jstring serviceNameObj,
        jboolean retry) {

    using ::android::hidl::base::V1_0::IBase;
    using ::android::hardware::details::getRawServiceInternal;

    std::string ifaceName;
    {
        ScopedUtfChars str(env, ifaceNameObj);
        if (str.c_str() == nullptr) {
            return nullptr;  // NPE will be pending.
        }
        ifaceName = str.c_str();
    }

    std::string serviceName;
    {
        ScopedUtfChars str(env, serviceNameObj);
        if (str.c_str() == nullptr) {
            return nullptr;  // NPE will be pending.
        }
        serviceName = str.c_str();
    }

    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, retry /* retry */, false /* getStub */);
    sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);

    if (service == NULL) {
        signalExceptionForError(env, NAME_NOT_FOUND);
        return NULL;
    }

    LOG(INFO) << "HwBinder: Starting thread pool for getting: " << ifaceName << "/" << serviceName;
    ::android::hardware::ProcessState::self()->startThreadPool();

    return JHwRemoteBinder::NewObject(env, service);
}

void JHwBinder_native_configureRpcThreadpool(JNIEnv *, jclass,
        jlong maxThreads, jboolean callerWillJoin) {
    CHECK(maxThreads > 0);
    ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);
}

void JHwBinder_native_joinRpcThreadpool() {
    IPCThreadState::self()->joinThreadPool();
}

static void JHwBinder_report_sysprop_change(JNIEnv * /*env*/, jclass /*clazz*/)
{
    report_sysprop_change();
}

static JNINativeMethod gMethods[] = {
    { "native_init", "()J", (void *)JHwBinder_native_init },
    { "native_setup", "()V", (void *)JHwBinder_native_setup },

    { "transact",
        "(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V",
        (void *)JHwBinder_native_transact },

    { "registerService", "(Ljava/lang/String;)V",
        (void *)JHwBinder_native_registerService },

    { "getService", "(Ljava/lang/String;Ljava/lang/String;Z)L" PACKAGE_PATH "/IHwBinder;",
        (void *)JHwBinder_native_getService },

    { "configureRpcThreadpool", "(JZ)V",
        (void *)JHwBinder_native_configureRpcThreadpool },

    { "joinRpcThreadpool", "()V",
        (void *)JHwBinder_native_joinRpcThreadpool },

    { "native_report_sysprop_change", "()V",
        (void *)JHwBinder_report_sysprop_change },
};

namespace android {

int register_android_os_HwBinder(JNIEnv *env) {
    jclass errorClass = FindClassOrDie(env, "java/lang/Error");
    gErrorClass = MakeGlobalRefOrDie(env, errorClass);

    return RegisterMethodsOrDie(env, CLASS_PATH, gMethods, NELEM(gMethods));
}

}  // namespace android
