#include "PhoneDevice.h"
#include <AndroidHelper.h>
#include <SpecData_JSON.h>
#include <Client/Terminal.h>
#include <Utils.h>
#include <LogThread.h>
#include "ncnn/yolov5ncnn.h"
#include "GPIOControl.h"
#include "CvText.h"
#include "PositionHelper.h"
#include "DngCreator.h"

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>

#include <opencv2/core/types.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <android/log.h>
#include <android/thermal.h>
#include <android/imagedecoder.h>
#include <sys/system_properties.h>
#include <media/NdkImage.h>
#include <mat.h>

#ifdef USING_HDRPLUS
#include <hdrplus/hdrplus_pipeline.h>
#include <hdrplus2/include/HDRPlus.h>
#endif

#include <fcntl.h>
#include <filesystem>
#include <cstdio>
#include <unistd.h>
namespace fs = std::filesystem;

#define CMD_SET_485_EN_STATE						131
#define CMD_SET_CAM_3V3_EN_STATE					132
#define CMD_SET_12V_EN_STATE						133

extern bool GetJniEnv(JavaVM *vm, JNIEnv **env, bool& didAttachThread);

#define WAKELOCK_NAME   "NDK_WK_"
// This value is 2 ^ 18 - 1, and is used to clamp the RGB values before their
// ranges
// are normalized to eight bits.
static const int kMaxChannelValue = 262143;

class ByteArraysPointer
{
public:
    ByteArraysPointer()
    {
    }
    ~ByteArraysPointer()
    {
#ifdef _DEBUG
        ALOGD("ByteArray Size=%u", (uint32_t)byteArrays.size());
        for (auto it = byteArrays.cbegin(); it != byteArrays.cend(); ++it)
        {
            ALOGD("ByteArray Free: Size=%u", (uint32_t)((*it).size()));
        }

#endif
        byteArrays.clear();
    }
    std::vector<std::vector<uint8_t> > byteArrays;
};

cv::Mat convert16bit2_8bit_(cv::Mat ans){
    if(ans.type()==CV_16UC3){
        cv::MatIterator_<cv::Vec3w> it, end;
        for( it = ans.begin<cv::Vec3w>(), end = ans.end<cv::Vec3w>(); it != end; ++it)
        {
            // std::cout<<sizeof (*it)[0] <<std::endl;
            (*it)[0] *=(255.0/USHRT_MAX);
            (*it)[1] *=(255.0/USHRT_MAX);
            (*it)[2] *=(255.0/USHRT_MAX);
        }
        ans.convertTo(ans, CV_8UC3);
    }else if(ans.type()==CV_16UC1){
        u_int16_t* ptr = (u_int16_t*)ans.data;
        int end = ans.rows*ans.cols;
        for(int i=0;i<end;i++){
            *(ptr+i) *=(255.0/USHRT_MAX);
        }
        ans.convertTo(ans, CV_8UC1);
    }else{
        // std::cout<<"Unsupported Data Type"<<std::endl;
    }
    return ans;
}

char* MakeArgv(const std::string v)
{
    char* argv = new char[v.size() + 1];
    memset(argv, 0, v.size() + 1);
    strcpy(argv, v.c_str());
    return argv;
}

static long getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], size_t num)
{
    int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC);

    if (fd < 0) {
        ALOGW("Unable to open /proc/meminfo");
        return -1;
    }

    char buffer[2048];
    const int len = read(fd, buffer, sizeof(buffer)-1);
    close(fd);

    if (len < 0) {
        ALOGW("Unable to read /proc/meminfo");
        return -1;
    }
    buffer[len] = 0;

    size_t numFound = 0;
    jlong mem = 0;

    char* p = buffer;
    while (*p && numFound < num) {
        int i = 0;
        while (sums[i]) {
            if (strncmp(p, sums[i], sumsLen[i]) == 0) {
                p += sumsLen[i];
                while (*p == ' ') p++;
                char* num = p;
                while (*p >= '0' && *p <= '9') p++;
                if (*p != 0) {
                    *p = 0;
                    p++;
                    if (*p == 0) p--;
                }
                mem += atoll(num) * 1024;
                numFound++;
                break;
            }
            i++;
        }
        p++;
    }

    return numFound > 0 ? mem : -1;
}

static jlong android_os_Process_getFreeMemory()
{
    static const char* const sums[] = { "MemFree:", "Cached:", NULL };
    static const size_t sumsLen[] = { strlen("MemFree:"), strlen("Cached:"), 0 };
    return getFreeMemoryImpl(sums, sumsLen, 2);
}

static jlong android_os_Process_getTotalMemory()
{
    static const char* const sums[] = { "MemTotal:", NULL };
    static const size_t sumsLen[] = { strlen("MemTotal:"), 0 };
    return getFreeMemoryImpl(sums, sumsLen, 1);
}

static inline uint32_t YUV2RGB(int nY, int nU, int nV) {
    nY -= 16;
    nU -= 128;
    nV -= 128;
    if (nY < 0) nY = 0;

    // This is the floating point equivalent. We do the conversion in integer
    // because some Android devices do not have floating point in hardware.
    // nR = (int)(1.164 * nY + 1.596 * nV);
    // nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU);
    // nB = (int)(1.164 * nY + 2.018 * nU);

    int nR = (int)(1192 * nY + 1634 * nV);
    int nG = (int)(1192 * nY - 833 * nV - 400 * nU);
    int nB = (int)(1192 * nY + 2066 * nU);

    nR = std::min(kMaxChannelValue, std::max(0, nR));
    nG = std::min(kMaxChannelValue, std::max(0, nG));
    nB = std::min(kMaxChannelValue, std::max(0, nB));

    nR = (nR >> 10) & 0xff;
    nG = (nG >> 10) & 0xff;
    nB = (nB >> 10) & 0xff;

    return 0xff000000 | (nR << 16) | (nG << 8) | nB;
}

CPhoneDevice::CPhoneCamera::CPhoneCamera(CPhoneDevice* dev, int32_t width, int32_t height, const NdkCamera::CAMERA_PARAMS& params) : NdkCamera(width, height, params), m_dev(dev)
{
}

CPhoneDevice::CPhoneCamera::~CPhoneCamera()
{
    m_dev = NULL;
}

bool CPhoneDevice::CPhoneCamera::on_image(cv::Mat rgb)
{
    if (m_dev != NULL)
    {
        return m_dev->OnImageReady(rgb);
    }

    return false;
}

bool CPhoneDevice::CPhoneCamera::onOneCapture(std::shared_ptr<ACameraMetadata> characteristics, std::shared_ptr<ACameraMetadata> result, uint32_t ldr, uint32_t duration, cv::Mat rgb)
{
    if (m_dev != NULL)
    {
        return m_dev->onOneCapture(characteristics, result, ldr, duration, rgb);
    }
    return false;
}

bool CPhoneDevice::CPhoneCamera::onBurstCapture(std::shared_ptr<ACameraMetadata> characteristics, std::vector<std::shared_ptr<ACameraMetadata> >& results, uint32_t ldr, uint32_t duration, std::vector<std::vector<uint8_t> >& frames)
{
    if (m_dev != NULL)
    {
        return m_dev->onBurstCapture(characteristics, results, ldr, duration, frames);
    }
    return false;
}

bool CPhoneDevice::CPhoneCamera::onBurstCapture(std::shared_ptr<ACameraMetadata> characteristics, std::vector<std::shared_ptr<ACameraMetadata> >& results, uint32_t ldr, uint32_t duration, std::vector<std::shared_ptr<AImage> >& frames)
{
    if (m_dev != NULL)
    {
        return m_dev->onBurstCapture(characteristics, results, ldr, duration, frames);
    }
    return false;
}

void CPhoneDevice::CPhoneCamera::on_error(const std::string& msg)
{
    if (m_dev != NULL)
    {
        m_dev->onError(msg);
    }
}

void CPhoneDevice::CPhoneCamera::onDisconnected(ACameraDevice* device)
{
    if (m_dev != NULL)
    {
        m_dev->onDisconnected(device);
    }
}


CPhoneDevice::CJpegCamera::CJpegCamera(CPhoneDevice* dev, int32_t width, int32_t height, const std::string& path, const NdkCamera::CAMERA_PARAMS& params) : CPhoneDevice::CPhoneCamera(dev, width, height, params), m_path(path)
{
}

bool CPhoneDevice::CJpegCamera::onOneCapture(std::shared_ptr<ACameraMetadata> characteristics, std::shared_ptr<ACameraMetadata> result, uint32_t ldr, uint32_t duration, cv::Mat rgb)
{
    if (m_dev != NULL)
    {
        return m_dev->onOneCapture(characteristics, result, ldr, duration, rgb);
    }
    return false;
}

bool CPhoneDevice::CJpegCamera::onBurstCapture(std::shared_ptr<ACameraMetadata> characteristics, std::vector<std::shared_ptr<ACameraMetadata> >& results, uint32_t ldr, uint32_t duration, std::vector<std::vector<uint8_t> >& frames)
{
    if (m_dev != NULL)
    {
        m_dev->onBurstCapture(characteristics, results, ldr, duration, frames);
    }
    return true;
}

bool CPhoneDevice::CJpegCamera::onBurstCapture(std::shared_ptr<ACameraMetadata> characteristics, std::vector<std::shared_ptr<ACameraMetadata> >& results, uint32_t ldr, uint32_t duration, std::vector<std::shared_ptr<AImage> >& frames)
{
    if (m_dev != NULL)
    {
        m_dev->onBurstCapture(characteristics, results, ldr, duration, frames);
    }
    return true;
}

void CPhoneDevice::CJpegCamera::onImageAvailable(AImageReader* reader)
{
    ALOGD("onImageAvailable %p", reader);

    AImage* image = 0;
    media_status_t mstatus = AImageReader_acquireLatestImage(reader, &image);

    if (mstatus != AMEDIA_OK)
    {
        // error
        // https://stackoverflow.com/questions/67063562
        if (mstatus != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE)
        {
            XYLOG(XYLOG_SEVERITY_ERROR, "AImageReader_acquireLatestImage error: %d", mstatus);
        }
        return;
    }

    uint8_t* y_data = 0;
    int y_len = 0;
#if 0
    if (!lightDetected)
    {
        AImage_getPlaneData(image, 0, &y_data, &y_len);

        lightDetected = true;

#if __cplusplus >= 201703L
        uint64_t avgY = std::reduce(y_data, y_data + y_len, 0);
#else
        uint64_t avgY = std::accumulate(y_data, y_data + y_len, 0);
#endif
        avgY = avgY / (uint64_t)y_len;
        mLdr = avgY;
#if 1
        if (avgY < 50)
        {
            if (m_params.autoExposure)
            {
                uint8_t aeMode = ACAMERA_CONTROL_AE_MODE_OFF;
                camera_status_t status = ACaptureRequest_setEntry_u8(capture_request, ACAMERA_CONTROL_AE_MODE, 1, &aeMode);

                int32_t sensitivity = (avgY < 5) ? 2000 : (mResult.sensitivity * 60.0 / avgY);
                status = ACaptureRequest_setEntry_i32(capture_request, ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity);

                int64_t exposureTime = (avgY < 5) ? 200 * 1000000 : (mResult.exposureTime * 120.0 / avgY);
                status = ACaptureRequest_setEntry_i64(capture_request, ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime);

                XYLOG(XYLOG_SEVERITY_WARNING, "YUV Light: %u EXPO:%lld => %lld ISO: %u => %u", (uint32_t)avgY,
                      mResult.exposureTime, exposureTime, mResult.sensitivity, sensitivity);
            }
            AImage_delete(image);
            return;
        }
#endif
    }
#endif

    int32_t format;
    AImage_getFormat(image, &format);

    if (format == AIMAGE_FORMAT_JPEG)
    {
        int planeCount;
        media_status_t status = AImage_getNumberOfPlanes(image, &planeCount);

        // LOGI("Info: getNumberOfPlanes() planeCount = %d", planeCount);
        if (!(status == AMEDIA_OK && planeCount == 1))
        {
            // LOGE("Error: getNumberOfPlanes() planeCount = %d", planeCount);
            return;
        }

        uint8_t *data = nullptr;
        int len = 0;
        AImage_getPlaneData(image, 0, &data, &len);

        FILE *file = fopen(m_path.c_str(), "wb");
        if (file && data && len)
        {
            fwrite(data, 1, len, file);
            fclose(file);
        }
        else
        {
            if (file)
                fclose(file);
        }
    }

    AImage_delete(image);
}

int32_t CPhoneDevice::CJpegCamera::getOutputFormat() const
{
    return AIMAGE_FORMAT_JPEG;
}

std::mutex CPhoneDevice::m_powerLocker;
long CPhoneDevice::mCameraPowerCount = 0;
long CPhoneDevice::mOtgCount = 0;

CPhoneDevice::CPhoneDevice(JavaVM* vm, jobject service, const std::string& appPath, unsigned int netId, unsigned int versionCode, const std::string& nativeLibDir) : mVersionCode(versionCode), m_nativeLibraryDir(nativeLibDir)
{
    mCamera = NULL;
    m_listener = NULL;
    m_pRecognizationCfg = NULL;
    mAIInitialized = false;
    mHeartbeatStartTime = 0;
    mHeartbeatDuration = 0;
    m_javaService = NULL;
    m_appPath = appPath;

    mNetId = netId;

    m_signalLevel = 0;
    m_signalLevelUpdateTime = time(NULL);
    mBuildTime = 0;

    RegisterHandlerForSignal(SIGUSR2);

    m_vm = vm;
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }
    if (service != NULL)
    {
        m_javaService = env->NewGlobalRef(service);

        jclass classService = env->GetObjectClass(m_javaService);
        mRegisterHeartbeatMid = env->GetMethodID(classService, "registerHeartbeatTimer", "(IJ)V");
        mUpdateTimeMid = env->GetMethodID(classService, "updateTime", "(J)Z");
        mUpdateCaptureScheduleMid = env->GetMethodID(classService, "updateCaptureSchedule", "(J)Z");
        mStartRecordingMid = env->GetMethodID(classService, "startRecording", "(ZIJIIIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");

        mRequestWakelockMid = env->GetMethodID(classService, "requestWakelock", "(Ljava/lang/String;J)V");
        mReleaseWakelockMid = env->GetMethodID(classService, "releaseWakelock", "(Ljava/lang/String;)V");

        mGetSystemInfoMid = env->GetMethodID(classService, "getSystemInfo", "()Ljava/lang/String;");
        mInstallAppMid = env->GetMethodID(classService, "installApp", "(Ljava/lang/String;J)Z");
        mRebootMid = env->GetMethodID(classService, "reboot", "(IJLjava/lang/String;)V");
        mEnableGpsMid = env->GetMethodID(classService, "enableGps", "(Z)V");
        mRequestPositionMid = env->GetMethodID(classService, "requestPosition", "()Z");

        mExecHdrplusMid = env->GetMethodID(classService, "execHdrplus", "(IILjava/lang/String;Ljava/lang/String;)I");

        mCallSysCameraMid = env->GetMethodID(classService, "callSystemCamera", "(IJ)V");

        env->DeleteLocalRef(classService);
    }

    if (didAttachThread)
    {
        vm->DetachCurrentThread();
    }

    m_timerUidFeed = time(NULL) * 1000;
    m_wakelockIdFeed = (unsigned long)m_timerUidFeed;
    m_uniqueIdFeed = (unsigned long)m_timerUidFeed;

#ifdef USING_NRSEC
    TurnOnCameraPower(env);
    GpioControl::setSpiPower(true);
#endif
}

CPhoneDevice::~CPhoneDevice()
{
    m_devLocker.lock();
    for (auto it = mTimers.begin(); it != mTimers.end(); ++it)
    {
        timer_delete((timer_t)it->first);
        delete it->second;
    }
    mTimers.clear();
    m_devLocker.unlock();

    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }
    env->DeleteGlobalRef(m_javaService);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }
    m_javaService = NULL;

    if (m_pRecognizationCfg != NULL)
    {
        if (mAIInitialized)
        {
            ncnn_uninit();
        }
        m_pRecognizationCfg = NULL;
    }
}

void CPhoneDevice::SetListener(IListener* listener)
{
    m_listener = listener;
}

void CPhoneDevice::SetRecognizationCfg(const IDevice::CFG_RECOGNIZATION* pRecognizationCfg)
{
    if (m_pRecognizationCfg == NULL && pRecognizationCfg != NULL && (pRecognizationCfg->enabled != 0))
    {
        // TODO
        std::string paramFile = m_appPath + (APP_PATH_RECOG_PARAM);
        std::string binFile = m_appPath + (APP_PATH_RECOG_BIN);
        std::error_code err;
        if (!existsFile(paramFile) || !existsFile(binFile) || fs::is_directory(fs::path(paramFile), err) || fs::is_directory(fs::path(binFile), err))
        {
            XYLOG(XYLOG_SEVERITY_WARNING, "AI Config Files are invalid");
        }
        else
        {
            XYLOG(XYLOG_SEVERITY_INFO, "AI Enabled and will Init NCNN");
            ncnn_init();
            mAIInitialized = true;
            bool res = YoloV5Ncnn_Init(paramFile, binFile);
            if (res)
            {
                XYLOG(XYLOG_SEVERITY_INFO, "Succeeded to Init NCNN");
            }
            else
            {
                XYLOG(XYLOG_SEVERITY_ERROR, "Failed to Init NCNN");
            }
        }
    }
    else
    {
        XYLOG(XYLOG_SEVERITY_WARNING, "AI Disabled");
    }

    m_pRecognizationCfg = pRecognizationCfg;
}

bool CPhoneDevice::BindNetwork(int sock)
{
    return true;
}

bool CPhoneDevice::SelfTest(std::string& result)
{
    result.clear();

    const char* ITEM_SEP = "\t"; //
    unsigned int numberOfChannels = 0;

    result += "设备自检 版本:" + GetVersion() + ITEM_SEP;

    Json::Value appConfig = Json::objectValue;
    std::vector<unsigned char> content;
    std::string filePath = m_appPath + (APP_DATA_DIR DIR_SEP_STR APP_FILE_NAME_APP_CONF);
    if (!readFile(filePath, content))
    {
        result += "读取系统配置文件App.json失败";
        result += ITEM_SEP;
    }
    else
    {
        Json::CharReaderBuilder builder;
        std::unique_ptr<Json::CharReader> reader(builder.newCharReader());

        const char* doc = (const char*)&(content[0]);
        if (reader->parse(doc, doc + content.size(), &appConfig, NULL))
        {
            unsigned int val = 0;
            if (GetJSONUInt32Value(appConfig, "channels", val) && (val > 0 && val <= 255))
            {
                numberOfChannels = val;
                result += "通道数：" + std::to_string(numberOfChannels) + ITEM_SEP;
            }
            else
            {
                result += "通道数未定义或者无效" + std::string(ITEM_SEP);
            }
        }
        else
        {
            result += "解析系统配置文件App.json失败" + std::string(ITEM_SEP);
        }
    }

    for (unsigned int channel = 1; channel <= numberOfChannels; channel++)
    {
        std::string path = m_appPath + (APP_PATH_CHANNELS DIR_SEP_STR);

        unsigned char cameraId = 0;
        unsigned char usbCamera = 0;
        Json::Value channelCfg = Json::objectValue;
        content.clear();
        filePath = m_appPath + (APP_DATA_DIR DIR_SEP_STR APP_FILE_NAME_APP_CONF);
        if (!readFile(filePath, content))
        {
            result += "读取通道" + std::to_string(channel) + "配置文件失败" + std::string(ITEM_SEP);
        }
        else
        {
            Json::CharReaderBuilder builder;
            std::unique_ptr<Json::CharReader> reader(builder.newCharReader());

            const char* doc = (const char*)&(content[0]);
            if (reader->parse(doc, doc + content.size(), &channelCfg, NULL))
            {
                GetJSONUInt8Value(channelCfg, "usbCamera", usbCamera);
                if (GetJSONUInt8Value(channelCfg, "cameraId", cameraId))
                {
                    result += "通道" + std::to_string(channel) + " Camera ID为 " + std::to_string(cameraId) + ITEM_SEP;
                }
                else
                {
                    cameraId = channel - 1;
                    result += "通道" + std::to_string(channel) + "未定义Camera ID, 使用默认值 " + std::to_string(cameraId) + ITEM_SEP;
                }
            }
            else
            {
                result += "解析通道" + std::to_string(channel) + "配置文件App.json失败" + std::string(ITEM_SEP);
            }
        }

        int32_t width = 0;
        int32_t height = 0;
        NdkCamera::CAMERA_PARAMS params = { 0 };
        params.burstCaptures = 1;
        if (usbCamera)
        {
            TurnOnOtg(NULL);
        }
        TurnOnCameraPower(NULL);

        NdkCamera camera(width, height, params);
        int res = camera.selfTest(std::to_string(cameraId), width, height);
        TurnOffCameraPower(NULL);
        if (usbCamera)
        {
            TurnOffOtg(NULL);
        }
        if (res == 0)
        {
            result += "通道" + std::to_string(channel) + "正常：最大分辨率：" + std::to_string(width) + "x" + std::to_string(height) + ITEM_SEP;
        }
        else
        {
            result += "通道" + std::to_string(channel) + " 异常 err=" + std::to_string(res) + ITEM_SEP;
        }
    }

    int bv = QueryBatteryVoltage(DEFAULT_BATTERY_QUERY_RETRIES);
    if (bv > 0)
    {
        bv -= bv % 100;
        result += std::string("电池电压：") + std::to_string(bv / 1000) + std::string(".") + std::to_string((bv % 1000) / 100) + ITEM_SEP;
    }

    fs::space_info si = fs::space("/data");
    double fr = ((double)si.available * 100.0f) / ((double)si.capacity);
    result += "可用存储：";
    result += std::to_string((int)fr);
    result += "%%" + std::string(ITEM_SEP);

    long fm = android_os_Process_getFreeMemory();
    long tm = android_os_Process_getTotalMemory();
    double fmp = ((double)fm * 100.0f) / ((double)tm);
    result += std::string("可用内存：") + std::to_string((int)fmp) + std::string("%%") + ITEM_SEP;

    if (!m_tfCardPath.empty())
    {
        fs::space_info si2 = fs::space(m_tfCardPath.c_str());
        double fr2 = ((double)si2.available * 100.0f) / ((double)si2.capacity);
        result += "TF卡可用空间：";
        result += std::to_string((int)fr2);
        result += "%%" + std::string(ITEM_SEP);
    }

    result += "4G信号强度：";
    result += std::to_string(m_signalLevel);
    result += ITEM_SEP;

    result += "网络接口：";
    std::vector<std::string> devices;
    GetNetDevices(devices);
    for (auto it = devices.cbegin(); it != devices.cend(); ++it)
    {
        result += (*it);
        result += " ";
    }
    // result += ITEM_SEP;

    return true;
}

bool CPhoneDevice::UpdateTime(time_t ts)
{
    JNIEnv* env = NULL;
    jboolean ret = JNI_FALSE;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }
    jlong timeInMillis = ((jlong)ts) * 1000;
    ret = env->CallBooleanMethod(m_javaService, mUpdateTimeMid, timeInMillis);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return (ret == JNI_TRUE);
}

bool CPhoneDevice::UpdateSchedules()
{
    JNIEnv* env = NULL;
    jboolean ret = JNI_FALSE;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }
    jlong ts = time(NULL);
    ret = env->CallBooleanMethod(m_javaService, mUpdateCaptureScheduleMid, ts);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return (ret == JNI_TRUE);
}

int CPhoneDevice::QueryBatteryVoltage(int retries)
{
    int val = -1;  // // BatVol
    for (int idx = 0; idx < retries; idx++)
    {
        val = GpioControl::getBatteryBusVoltage();  // // BatVol
        if (val >= 0)
        {
            break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    return val;
}

bool CPhoneDevice::QuerySystemProperties(std::map<std::string, std::string>& properties)
{
    char value[PROP_VALUE_MAX] = { 0 };
    std::map<std::string, std::string> powerInfo;
    int res = 0;
    int bv = -1;

    for (std::map<std::string, std::string>::iterator it = properties.begin(); it != properties.end(); ++it)
    {
        if (!(it->second.empty()))
        {
            continue;
        }

        if (it->first == PROP_EQUIP_NAME)
        {
            __system_property_get("ro.product.name", value);
            it->second = value;
        }
        else if (it->first == PROP_MODEL)
        {
            __system_property_get("ro.product.model", value);
            it->second = std::string(value);
        }
        else if (it->first == PROP_BS_MANU)
        {
            __system_property_get("ro.product.manufacturer", value);
            it->second = std::string(value);
        }
        else if (it->first == PROP_VERSION)
        {
            // FOR Protocol
            snprintf(value, sizeof(value), "%u.%03u", (mVersionCode / 1000), (mVersionCode % 1000));
            // __system_property_get("ro.build.version.release", value);
            it->second = std::string(value);
        }
        else if (it->first == (PROP_VERSION_ABBR))
        {
            // FOR OSD
            string version = GetVersion();
#if 0
            version += " " + FormatLocalTime(mBuildTime);
#endif
            it->second = version;
        }
        else if (it->first == PROP_BUILD_TIME)
        {
            it->second = FormatLocalDateTime(mBuildTime);
        }
        else if (it->first == PROP_PROD_DATE)
        {
            __system_property_get("ro.build.date.utc", value);
            it->second = std::string(value);
        }
        else if (it->first == PROP_SN || it->first == PROP_BS_ID)
        {
            __system_property_get("ro.serialno", value);
            it->second = std::string(value);
        }
        else if (it->first == PROP_IMEI)
        {
            if (m_simcard.empty())
            {
                __system_property_get("phone.imei", value);
                it->second = std::string(value);
            }
            else
            {
                it->second = m_simcard;
            }
        }
        else if (it->first == PROP_OPERATION_TEMP)
        {
            it->second = QueryCpuTemperature();
        }
        else if (it->first == PROP_FREE_ROM)
        {
            fs::space_info si = fs::space("/data");
            it->second = std::to_string(si.available);  // Unit: M
        }
        else if (it->first == PROP_FREE_ROM_PERCENT)
        {
            fs::space_info si = fs::space("/data");
            double fr = ((double)si.available * 100.0f) / ((double)si.capacity);
            snprintf(value, sizeof(value), "%d%%", (int)fr);
            it->second = std::string(value);
        }
        else if (it->first == PROP_TOTAL_ROM)
        {
            fs::space_info si = fs::space("/data");
            it->second = std::to_string(si.capacity);   // Unit: M
        }
        else if (it->first == PROP_FREE_MEMORY)
        {
            it->second = std::to_string(android_os_Process_getFreeMemory());  // Unit: M
        }
        else if (it->first == PROP_FREE_MEMORY_PERCENT)
        {
            long fm = android_os_Process_getFreeMemory();
            long tm = android_os_Process_getTotalMemory();
            double fmp = ((double)fm * 100.0f) / ((double)tm);
            snprintf(value, sizeof(value), "%d%%", (int)fmp);
            it->second = std::string(value);  // Unit: M
        }
        else if (it->first == PROP_TOTAL_MEMORY)
        {
            it->second = std::to_string(android_os_Process_getTotalMemory());  // Unit: M
        }
        else if (it->first == (PROP_LIGHTDEPENDENT_RESISTOR))
        {
            int val = GpioControl::getLightAdc();
            it->second = std::to_string(val);
        }
        else if (it->first == (PROP_CHARGING_CURRENT))
        {
            it->second = std::to_string(GpioControl::getChargingCurrent());
        }
        else if (it->first == (PROP_CHARGING_POWER))
        {
            it->second = std::to_string(GpioControl::getChargingPower());
        }
        else if (it->first == (PROP_CHARGING_BUS_VOL) || it->first == (PROP_CHARGING_VOLTAGE))
        {
            double val = -1;
            char str[32] = { 0 };
            for (int idx = 0; idx < 3; idx++)
            {
                val = GpioControl::getChargingBusVoltage();
                if (val < 0)
                {
                    continue;
                }
                snprintf(str, sizeof(str), "%.1f", (val / 1000.0));
                it->second = std::string(str);
                break;
            }
        }
        else if (it->first == (PROP_BATTERY_POWER))
        {
            it->second = std::to_string(GpioControl::getBatteryPower());
        }
        else if (it->first == (PROP_BATTERY_BUS_VOL) || it->first == (PROP_BATTERY_VOLTAGE))
        {
            int val = QueryBatteryVoltage(DEFAULT_BATTERY_QUERY_RETRIES);  // // BatVol
            if (val > 0)
            {
                bv = val;
                snprintf(value, sizeof(value), "%.1f", val / 1000.0);
                it->second = std::string(value);
            }
            else
            {
#ifdef _DEBUG
                int aa = 0;
#endif
            }
        }
        else if ((it->first == (PROP_SIGNAL_4G)) || (it->first == (PROP_SIGNAL_2G)) || (it->first == (PROP_SIGNAL_LEVEL)))
        {
            it->second = std::to_string(m_signalLevel);
        }
        /*
        else if (startsWith(it->first, PROP_JAVA_PREFIX))
        {
            if (powerInfo.empty())
            {
                QueryPowerInfo(powerInfo);
            }
            auto it2 = powerInfo.find(it->first);
            if (it2 != powerInfo.cend())
            {
                it->second = it2->second;
            }
        }
         */
    }

    std::map<std::string, std::string>::iterator it = properties.find(PROP_BATTERY_CURRENT);
    if (it != properties.end())
    {
        if (bv == -1)
        {
            bv = QueryBatteryVoltage(DEFAULT_BATTERY_QUERY_RETRIES);
        }

        if (bv > 0)
        {
            char str[32] = { 0 };
            float batteryCurrent = STANDARD_CURRENT_64V / ((float)bv / 1000.0f / STANDARD_VOLTAGE_64V);
            snprintf(str, sizeof(str), "%d", (int)batteryCurrent);
            it->second = std::string(str);
        }
    }
    // __system_property_get("ro.telephony.default_network", value);

    return true;
}

std::string CPhoneDevice::QueryCpuTemperature()
{
    // /sys/devices/virtual/thermal/thermal_zone0/temp
    std::vector<unsigned char> data;
    //  /sys/class/thermal/thermal zone*/temp
    if (readFile("/sys/class/thermal/thermal_zone3/temp", data) && !data.empty())
    {
        data.push_back(0);
        int temp = atoi((const char*)(&data[0]));
        return std::to_string((temp / 1000) + 20);
    }

    return "";
}

void CPhoneDevice::QueryPowerInfo(std::map<std::string, std::string>& powerInfo)
{
    JNIEnv* env = NULL;
    jboolean ret = JNI_FALSE;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }
    jobject jobj = env->CallObjectMethod(m_javaService, mGetSystemInfoMid);
    std::string str = jstring2string(env, (jstring)jobj);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    if (!str.empty())
    {
        std::map<std::string, std::string> queries = parseQuery(str);
        powerInfo.swap(queries);
    }
}

bool CPhoneDevice::GetNextScheduleItem(uint32_t tsBasedZero, uint32_t scheduleTime, vector<uint32_t>& items)
{
    return false;
}

bool CPhoneDevice::InstallAPP(const std::string& path, unsigned int delayedTime)
{
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }

    jstring jpath = env->NewStringUTF(path.c_str());
    env->CallBooleanMethod(m_javaService, mInstallAppMid, jpath, (jlong)delayedTime);
    // env->ReleaseStringUTFChars(jpath, path.c_str());
    env->DeleteLocalRef(jpath);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return true;
}

bool CPhoneDevice::Reboot(int resetType, const std::string& reason)
{
    if (resetType == REBOOT_TYPE_DEVICE)
    {
        // reboot the device
        std::thread t([]()
          {
              std::this_thread::sleep_for(std::chrono::milliseconds(1000));
              GpioControl::reboot();
          });
        t.detach();
    }
    else
    {
        long timeout = 1000;
        RestartApp(resetType, timeout, reason);
    }

    return true;
}

void CPhoneDevice::RestartApp(int resetType, long timeout, const std::string& reason)
{
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }

    jstring jreason = NULL;
    if (!reason.empty())
    {
        jreason = env->NewStringUTF(reason.c_str());
    }
    env->CallVoidMethod(m_javaService, mRebootMid, resetType, (jlong)timeout, jreason);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }
}

bool CPhoneDevice::EnableGPS(bool enabled)
{
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
        return false;
    }
    jboolean jenabled = enabled ? JNI_TRUE : JNI_FALSE;
    env->CallVoidMethod(m_javaService, mEnableGpsMid, jenabled);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return true;
}

float CPhoneDevice::QueryBattaryVoltage(int timesForAvg, bool* isCharging)
{
    if (timesForAvg <= 0)
    {
        return 0.0f;
    }

    int val = 0;
    int totalVals = 0;
    float chargingBusVoltage = 0.0f;
    for (int idx = 0; idx < timesForAvg; idx++)
    {
        val = GpioControl::getChargingBusVoltage();
        if (val > 1000)
        {
            chargingBusVoltage = (float)val / 1000.0;
            break;
        }
    }

    if (isCharging != NULL)
    {
        *isCharging = chargingBusVoltage > DEFAULT_WARNING_CHARGING_BUS_VOL;
    }

    int matched = 0;
    for (int idx = 0; idx < timesForAvg; idx++)
    {
        val = GpioControl::getBatteryVoltage();  // // BatVol
        if (val > 0)
        {
            totalVals += val > BATTARY_VOLTAGE_MAX ? BATTARY_VOLTAGE_MAX : val;
            matched++;
        }
    }

    return (matched > 0) ? ((float)totalVals / 1000.0 / matched) : 0;
}

bool CPhoneDevice::RequestPosition()
{
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
        return false;
    }

    jboolean ret = env->CallBooleanMethod(m_javaService, mRequestPositionMid);
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return (ret == JNI_TRUE);
}

void CPhoneDevice::handleSignal(int sig, siginfo_t *si, void *uc)
{
    TIMER_CONTEXT* context = (TIMER_CONTEXT*)(si->si_value.sival_ptr);
    context->device->handleTimerImpl(context);
}

bool CPhoneDevice::RegisterHandlerForSignal(int sig)
{
    return true;
    // Establish handler for timer signal
    struct sigaction sa;
    sigset_t mask;

    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = CPhoneDevice::handleSignal;
    sigemptyset(&sa.sa_mask);
    if (sigaction(sig, &sa, NULL) == -1)
    {
        return false;
    }

    return true;
    // Block timer signal temporarily

    // printf("Blocking signal %d\n", SIG);
    sigemptyset(&mask);
    sigaddset(&mask, sig);
    if (sigprocmask(SIG_SETMASK, &mask, NULL) == -1)
    {
        return false;
    }

    return true;
}

void CPhoneDevice::handleTimer(union sigval v)
{
    TIMER_CONTEXT* context = (TIMER_CONTEXT*)(v.sival_ptr);
    context->device->handleTimerImpl(context);
}

void CPhoneDevice::handleTimerImpl(CPhoneDevice::TIMER_CONTEXT* context)
{
    context->times++;
    if (context->expectedTimes == 0 || context->times <= context->expectedTimes)
    {
        if (m_listener != NULL)
        {
            m_listener->OnTimeout(context->uid, context->timerType, context->data, context->times);
        }
    }
}

void CPhoneDevice::handleRebootTimer(union sigval v)
{
    CPhoneDevice* pDevice = (CPhoneDevice*)(v.sival_ptr);
    // Reboot APP
    XYLOG(XYLOG_SEVERITY_ERROR, "Camera Close Thread is DEAD, will RESTART app");
    pDevice->RestartApp(0, 2000, "Camera Can't Close");
}

// void CPhoneDevice::handleRebootTimerImpl()
// {
// }

IDevice::timer_uid_t CPhoneDevice::RegisterTimer(unsigned int timerType, unsigned int timeout, void* data, unsigned long times/* = 0*/)
{
    struct sigevent evp = { 0 };
    struct itimerspec ts = { 0 };
    timer_t timer;
    int ret;

    TIMER_CONTEXT* context = new TIMER_CONTEXT();
    context->device = this;
    context->data = data;
    context->timerType = timerType;
    context->expectedTimes = times;
    context->times = 0;
    context->uid = 0;

    evp.sigev_value.sival_ptr = context;
    evp.sigev_notify = SIGEV_THREAD; //SIGEV_THREAD_ID;
    evp.sigev_notify_function = CPhoneDevice::handleTimer;
    // evp.sigev_notify_thread_id = gettid();
    // evp.sigev_notify = SIGEV_SIGNAL;
    // evp.sigev_signo = SIGUSR2;

    ret = timer_create(CLOCK_REALTIME, &evp, &timer);
    if( ret)
    {
        int err = errno;
        delete context;
        return INVALID_TIMER_UID;
    }

    context->uid = (unsigned long)timer;
    ts.it_value.tv_sec = (timeout / 1000);
    ts.it_value.tv_nsec = (timeout % 1000) * 1000;
    if (times != 1)
    {
        ts.it_interval.tv_sec = ts.it_value.tv_sec;
        ts.it_interval.tv_nsec = ts.it_value.tv_nsec;
    }

    ret = timer_settime(timer, 0, &ts, NULL);
    if(ret)
    {
        timer_delete(timer);
        delete context;
        return INVALID_TIMER_UID;
    }

    m_devLocker.lock();
    mTimers.insert(mTimers.end(), std::pair<IDevice::timer_uid_t, TIMER_CONTEXT*>((IDevice::timer_uid_t)timer, context));
    m_devLocker.unlock();
    return (IDevice::timer_uid_t)timer;
}

bool CPhoneDevice::UnregisterTimer(IDevice::timer_uid_t uid)
{
    timer_t timer = (timer_t)uid;
    int res = timer_delete(timer);

    m_devLocker.lock();
    std::map<IDevice::timer_uid_t, TIMER_CONTEXT*>::iterator it = mTimers.find(uid);
    if (it != mTimers.end())
    {
        delete it->second;
        mTimers.erase(it);
        m_devLocker.unlock();
        return true;
    }

    m_devLocker.unlock();
    return false;
}

unsigned long CPhoneDevice::RequestWakelock(unsigned long timeout)
{
    unsigned long wakelockId = m_wakelockIdFeed.fetch_add(1);
    std::string name = WAKELOCK_NAME;
    name += to_string(wakelockId);

    // ALOGI("RequestWakelock=%lld",wakelockId);

    jboolean ret = JNI_FALSE;
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
        return 0;
    }
    jstring jname = env->NewStringUTF(name.c_str());
    jlong jtimeout = (jlong)timeout;

    env->CallVoidMethod(m_javaService, mRequestWakelockMid, jname, jtimeout);
    // env->ReleaseStringUTFChars(jname, name.c_str());
    env->DeleteLocalRef(jname);

    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return wakelockId;
}

bool CPhoneDevice::ReleaseWakelock(unsigned long wakelock)
{
    // ALOGI("ReleaseWakelock=%lld", wakelock);
    std::string name = WAKELOCK_NAME;
    name += to_string(wakelock);

    jboolean ret = JNI_FALSE;
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
        return false;
    }
    jstring jname = env->NewStringUTF(name.c_str());

    env->CallVoidMethod(m_javaService, mReleaseWakelockMid, jname);
    env->DeleteLocalRef(jname);
    // env->ReleaseStringUTFChars(jname, name.c_str());
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return true;
}

IDevice::timer_uid_t CPhoneDevice::RegisterHeartbeat(unsigned int timerType, unsigned int timeout, time_t tsForNextPhoto)
{
    mHeartbeatStartTime = time(NULL);
    mHeartbeatDuration = timeout;

    IDevice::timer_uid_t uid = m_timerUidFeed.fetch_add(1);

    jboolean ret = JNI_FALSE;
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
        return 0;
    }
#ifdef ALIGN_HB_TIMER_TO_PHOTO
    env->CallVoidMethod(m_javaService, mRegisterHeartbeatMid, (jint)timeout, (jlong)tsForNextPhoto);
#else
    env->CallVoidMethod(m_javaService, mRegisterHeartbeatMid, (jint)timeout, 0);
#endif
    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return uid;
}

bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const vector<OSD_INFO>& osds, const std::string& path)
{
    if (photoInfo.width == 0 || photoInfo.height == 0)
    {
        XYLOG(XYLOG_SEVERITY_ERROR, "TP: Invalid Size: (%u-%u) PHOTOID=%u", (unsigned int)photoInfo.width, (unsigned int)photoInfo.height, photoInfo.photoId);
        return false;
    }
    if (m_threadClose.joinable())
    {
        XYLOG(XYLOG_SEVERITY_INFO, "TP: Wait Prev Thread CH=%u PR=%X PHOTOID=%u", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId);
        struct sigevent evp = { 0 };
        struct itimerspec ts = { 0 };
        timer_t timer;
        int ret;

        evp.sigev_value.sival_ptr = this;
        evp.sigev_notify = SIGEV_THREAD; //SIGEV_THREAD_ID;
        evp.sigev_notify_function = CPhoneDevice::handleRebootTimer;
        // evp.sigev_notify_thread_id = gettid();
        // evp.sigev_notify = SIGEV_SIGNAL;
        // evp.sigev_signo = SIGUSR2;

        ret = timer_create(CLOCK_REALTIME, &evp, &timer);
        if( ret == 0)
        {
            ts.it_value.tv_sec = 8; // 8 seconds
            ts.it_value.tv_nsec = 0;
            ret = timer_settime(timer, 0, &ts, NULL);
        }
        m_threadClose.join();
        timer_delete(timer);
        XYLOG(XYLOG_SEVERITY_INFO, "TP: Wait Prev Thread End CH=%u PR=%X PHOTOID=%u", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId);
    }

    if (mCamera != NULL)
    {
        XYLOG(XYLOG_SEVERITY_INFO, "TP: mCamera ISNOT null CH=%u PR=%X PHOTOID=%u", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId);
        delete mCamera;
        mCamera = NULL;
    }

    XYLOG(XYLOG_SEVERITY_INFO, "TP: CH=%u PR=%X PHOTOID=%u", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId);
    mPhotoInfo = photoInfo;
    mPath = path;
    mOsds = osds;

    NdkCamera::CAMERA_PARAMS params;
    memset(&params, 0, sizeof(params));

    params.sceneMode = mPhotoInfo.sceneMode;
    params.autoFocus = mPhotoInfo.autoFocus;
    params.autoExposure = mPhotoInfo.autoExposure;
    params.focusTimeout = mPhotoInfo.focusTimeout * 1000;
    params.exposureTime = mPhotoInfo.exposureTime;
    params.sensitivity = mPhotoInfo.sensitivity;
    params.compensation = mPhotoInfo.compensation;
    params.orientation = mPhotoInfo.orientation;
    params.zoom = mPhotoInfo.zoom;
    params.zoomRatio = mPhotoInfo.zoomRatio;
    params.requestTemplate = mPhotoInfo.requestTemplate;
    params.awbMode = mPhotoInfo.awbMode;
    params.wait3ALocked = mPhotoInfo.wait3ALocked;
    params.burstRawCapture = mPhotoInfo.usingRawFormat;
    params.burstCaptures = mPhotoInfo.burstCaptures;
    if (params.requestTemplate <= 0 || params.requestTemplate > 5)
    {
        params.requestTemplate = 2;
    }

#if 0
    if (photoInfo.ldrEnabled)
    {
        if (GpioControl::getLightAdc() > 1400)
        {
            params.autoExposure = 0;
            params.exposureTime = 1200000000;
            params.sensitivity = 1200;
        }
    }
#endif

    bool res = false;

    if (photoInfo.usbCamera)
    {
        TurnOnOtg(NULL);
    }
    TurnOnCameraPower(NULL);

    res = true;
    if (mPhotoInfo.mediaType == 0 && mPhotoInfo.usingSysCamera == 0)
    {
        mCamera = new CPhoneCamera(this, photoInfo.width, photoInfo.height, params);
        // mCamera = new CJpegCamera(this, photoInfo.width, photoInfo.height, mPath, params);
        if (mCamera->open(to_string(mPhotoInfo.cameraId)) == 0)
        {
            XYLOG(XYLOG_SEVERITY_DEBUG, "TP: Succeeded to OpenCamera CH=%u PR=%X PHOTOID=%u", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId);
        }
        else
        {
            bool hasFatalError = mCamera->HasFatalError();
            XYLOG(XYLOG_SEVERITY_DEBUG, "TP: Failed to OpenCamera CH=%u PR=%X PHOTOID=%u", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId);
            delete mCamera;
            mCamera = NULL;
            res = false;

            TurnOffCameraPower(NULL);
            if (photoInfo.usbCamera)
            {
                TurnOffOtg(NULL);
            }

            if (hasFatalError)
            {
                XYLOG(XYLOG_SEVERITY_DEBUG, "TP: Fatal Error Happened, will RestartAPP in 60s CH=%u PR=%X PHOTOID=%u", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId);
                RestartApp(REBOOT_TYPE_APP, 60000, "FatalErrorOnCamera");
            }
        }
    }
    else if (mPhotoInfo.usingSysCamera == 1)
    {
        JNIEnv* env = NULL;
        bool didAttachThread = false;
        res = GetJniEnv(m_vm, &env, didAttachThread);
        if (!res)
        {
            ALOGE("Failed to get JNI Env");
            return false;
        }

        IDevice::PHOTO_INFO *pPhotoInfo = new IDevice::PHOTO_INFO(mPhotoInfo);

        jboolean photoOrVideo = mPhotoInfo.mediaType == 0 ? JNI_TRUE : JNI_FALSE;
        env->CallVoidMethod(m_javaService, mCallSysCameraMid, mPhotoInfo.cameraId,
                            reinterpret_cast<jlong>(pPhotoInfo));

        if (didAttachThread)
        {
            m_vm->DetachCurrentThread();
        }
    }
    else
    {
        JNIEnv* env = NULL;
        bool didAttachThread = false;
        res = GetJniEnv(m_vm, &env, didAttachThread);
        if (!res)
        {
            ALOGE("Failed to get JNI Env");
            return false;
        }

        jstring leftTopOSD = NULL;
        jstring rightTopOSD = NULL;
        jstring rightBottomOSD = NULL;
        jstring leftBottomOSD = NULL;

        for (vector<OSD_INFO>::const_iterator it = mOsds.cbegin(); it != mOsds.cend(); ++it)
        {
            if (it->text.empty())
            {
                continue;
            }
            switch (it->alignment)
            {
                case OSD_ALIGNMENT_TOP_LEFT:
                    leftTopOSD = env->NewStringUTF(it->text.c_str());
                    break;
                case OSD_ALIGNMENT_TOP_RIGHT:
                    rightTopOSD = env->NewStringUTF(it->text.c_str());
                    break;
                case OSD_ALIGNMENT_BOTTOM_RIGHT:
                    rightBottomOSD = env->NewStringUTF(it->text.c_str());
                    break;
                case OSD_ALIGNMENT_BOTTOM_LEFT:
                    leftBottomOSD = env->NewStringUTF(it->text.c_str());
                    break;
            }
        }

        int orientation = mPhotoInfo.orientation == 0 ? -1 : (mPhotoInfo.orientation - 1) * 90;
        jboolean photoOrVideo = mPhotoInfo.mediaType == 0 ? JNI_TRUE : JNI_FALSE;
        env->CallVoidMethod(m_javaService, mStartRecordingMid, photoOrVideo, mPhotoInfo.cameraId, (unsigned long)mPhotoInfo.photoId,
                            mPhotoInfo.duration, mPhotoInfo.width, mPhotoInfo.height, mPhotoInfo.duration, orientation,
                            leftTopOSD, rightTopOSD, rightBottomOSD, leftBottomOSD);

        if (leftTopOSD) env->DeleteLocalRef(leftTopOSD);
        if (rightTopOSD) env->DeleteLocalRef(rightTopOSD);
        if (rightBottomOSD) env->DeleteLocalRef(rightBottomOSD);
        if (leftBottomOSD) env->DeleteLocalRef(leftBottomOSD);

        if (didAttachThread)
        {
            m_vm->DetachCurrentThread();
        }
    }

    return res;
}

bool CPhoneDevice::CloseCamera()
{
    if (mCamera != NULL)
    {
        auto camera = mCamera;
        mCamera = NULL;

        camera->close();
        delete camera;
    }
    return true;
}

void CPhoneDevice::CloseCamera2(CPhoneDevice::CPhoneCamera* camera, unsigned int photoId, bool turnOffOtg)
{
    XYLOG(XYLOG_SEVERITY_DEBUG, "TP: Start CloseCamera PHOTOID=%u", photoId);
    // std::this_thread::sleep_for(std::chrono::milliseconds(16));
    if (camera != NULL)
    {
        camera->close();
        delete camera;
    }

    XYLOG(XYLOG_SEVERITY_DEBUG, "TP: Will Turn Off Power PHOTOID=%u", photoId);
    if (turnOffOtg)
    {
        TurnOffOtg(NULL);
    }
    TurnOffCameraPower(NULL);
    XYLOG(XYLOG_SEVERITY_DEBUG, "TP: End Turn Off Power PHOTOID=%u", photoId);

    XYLOG(XYLOG_SEVERITY_DEBUG, "TP: CloseCamera PHOTOID=%u", photoId);

}

void visualize(const char* filename, const ncnn::Mat& m)
{
    cv::Mat a(m.h, m.w, CV_8UC3);
    m.to_pixels(a.data, ncnn::Mat::PIXEL_BGR2RGB);

    cv::imwrite(filename, a);
}

void DrawOutlineText(cv::Ptr<cv::ft::FreeType2> ft2, cv::Mat& mat, const std::string& str, cv::Point startPoint, int fontSize, cv::Scalar clr, int thickness)
{
    std::vector<std::string> lines = split(str, "\n");
    int lineHeight = 0;
    cv::Point pt = startPoint;
    cv::Size textSize;
    int baseline = 0;

    for (std::vector<std::string>::const_iterator it = lines.cbegin(); it != lines.cend(); ++it )
    {
        std::string trimmedLine = *it;
        trimString(trimmedLine);
        textSize = ft2->getTextSize(trimmedLine, fontSize, thickness, &baseline);
        lineHeight = std::max(fontSize, textSize.height + baseline);

        ft2->putText(mat, trimmedLine, pt, fontSize, clr, thickness, cv::LINE_AA, false, true);

        pt.x = startPoint.x;
        pt.y += lineHeight + (lineHeight >> 2);    // 125%
    }
}

bool CPhoneDevice::onOneCapture(std::shared_ptr<ACameraMetadata> characteristics,
                                  std::shared_ptr<ACameraMetadata> result,
                                  uint32_t ldr, uint32_t duration, cv::Mat rgb)
{
    time_t takingTime = time(NULL);
    if (mPhotoInfo.remedy != 0)
    {
        if ((takingTime - mPhotoInfo.scheduleTime) > 30)
        {
            takingTime = mPhotoInfo.scheduleTime + mPhotoInfo.channel * 2;
        }
    }
    mPhotoInfo.photoTime = takingTime;

    vector<IDevice::OSD_INFO> osds;
    osds.swap(mOsds);
    PHOTO_INFO photoInfo = mPhotoInfo;
    std::string path;
    path.swap(mPath);

    // std::string tmpPath = m_appPath + std::string(APP_DIR_TMP DIR_SEP_STR) + std::to_string(photoInfo.photoId);

    acamera_metadata_enum_android_lens_facing_t facing = ACAMERA_LENS_FACING_FRONT;
    ACameraMetadata_const_entry e = { 0 };
    camera_status_t status = ACameraMetadata_getConstEntry(characteristics.get(), ACAMERA_LENS_FACING, &e);
    if (status == ACAMERA_OK)
    {
        facing = (acamera_metadata_enum_android_lens_facing_t)e.data.u8[0];
    }

    int sensorOrientation = 0;
    {
        ACameraMetadata_const_entry e = { 0 };
        status = ACameraMetadata_getConstEntry(characteristics.get(), ACAMERA_SENSOR_ORIENTATION, &e);
        if (status == ACAMERA_OK)
        {
            sensorOrientation = (int)e.data.i32[0];
        }
    }

    bool turnOffOtg = (photoInfo.usbCamera != 0);
    CPhoneCamera* pCamera = mCamera;
    mCamera = NULL;

    media_status_t mstatus;

    std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, photoInfo.photoId, turnOffOtg);
    m_threadClose.swap(closeThread);
    if (closeThread.joinable())
    {
        closeThread.detach();
    }

    CPhoneDevice* pThis = this;
    std::thread th([pThis, characteristics, result, photoInfo, osds, path, rgb, facing, sensorOrientation, ldr, duration, takingTime]()mutable
                   {
                       std::string cameraInfo;
                       if (photoInfo.outputDbgInfo != 0)
                       {
                           NdkCamera::CAPTURE_RESULT captureResult = { 0 };
                           NdkCamera::EnumCameraResult(result.get(), captureResult);

                           char extimeunit[4] = { 0 };
                           unsigned int extime = (captureResult.exposureTime >= 1000000) ? ((unsigned int)(captureResult.exposureTime / 1000000)) : ((unsigned int)(captureResult.exposureTime / 1000));
                           strcpy(extimeunit, (captureResult.exposureTime >= 1000000) ? "ms" : "μs");
                           char str[128] = { 0 };
                           snprintf(str, sizeof(str), "AE=%u AF=%u EXPS=%u%s(%d) ISO=%d AFS=%u AES=%u AWBS=%u SCENE=%d LDR=%d(%u) %0.1fx T=%u FD=%lld",
                                    captureResult.autoExposure, captureResult.autoFocus,
                                    extime, extimeunit, captureResult.compensation, captureResult.sensitivity,
                                   // isnan(captureResult.FocusDistance) ? 0 : captureResult.FocusDistance,
                                    (unsigned int)captureResult.afState, (unsigned int)captureResult.aeState, captureResult.awbState,
                                    captureResult.sceneMode, GpioControl::getLightAdc(), ldr, captureResult.zoomRatio,
                                    duration, captureResult.frameDuration);
                           cameraInfo = str;
                       }

#ifdef OUTPUT_CAMERA_DBG_INFO
#if 0
            bool shouldRetry = false;
            if (ldr != ~0)
            {
                if (ldr < MIN_LIGHT_Y)
                {
                    if (photoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
                    {
                        shouldRetry = true;
                        char presetBuf[16] = {0};
                        snprintf(presetBuf, sizeof(presetBuf), "%02X", photoInfo.retries);
                        // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(photoInfo.retries) + ".jpg");
                        replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                        XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                              (uint32_t) captureResult.avgY);

                        // photoInfo.usingRawFormat = 1;
                    }
                }
                else if (ldr > MAX_LIGHT_Y)
                {
                    if (photoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
                    {
                        shouldRetry = true;
                        char presetBuf[16] = {0};
                        snprintf(presetBuf, sizeof(presetBuf), "%02X", photoInfo.retries);
                        // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(photoInfo.retries) + ".jpg");
                        replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                        XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                              (uint32_t) captureResult.avgY);
                    }

                    photoInfo.compensation = -2 * ((int16_t) ((uint16_t) captureResult.avgY));
                }
            }
#endif // 0
#endif // OUTPUT_CAMERA_DBG_INFO

                       // Notify to take next photo
                       pThis->TakePhotoCb(1, photoInfo, "", takingTime);

                       bool res = pThis->PostProcessPhoto(photoInfo, osds, path, cameraInfo, rgb);
                       if (res)
                       {
                           // TakePhotoCb(2, photoInfo, path, takingTime);
                       }
                   });

    th.detach();

    return true;
}

bool CPhoneDevice::onBurstCapture(std::shared_ptr<ACameraMetadata> characteristics,
                                  std::vector<std::shared_ptr<ACameraMetadata> >& results,
                                  uint32_t ldr, uint32_t duration, std::vector<std::vector<uint8_t> >& frames)
{
    time_t takingTime = time(NULL);
    if (mPhotoInfo.remedy != 0)
    {
        if ((takingTime - mPhotoInfo.scheduleTime) > 30)
        {
            takingTime = mPhotoInfo.scheduleTime + mPhotoInfo.channel * 2;
        }
    }
    mPhotoInfo.photoTime = takingTime;

    vector<IDevice::OSD_INFO> osds;
    osds.swap(mOsds);
    PHOTO_INFO photoInfo = mPhotoInfo;
    std::string path;
    path.swap(mPath);

    // std::string tmpPath = m_appPath + std::string(APP_DIR_TMP DIR_SEP_STR) + std::to_string(photoInfo.photoId);
    std::shared_ptr<ByteArraysPointer> pByteArrays = std::make_shared<ByteArraysPointer>();
    pByteArrays.get()->byteArrays.swap(frames);

    bool turnOffOtg = (photoInfo.usbCamera != 0);
    CPhoneCamera* pCamera = mCamera;
    mCamera = NULL;

    std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, photoInfo.photoId, turnOffOtg);
    m_threadClose.swap(closeThread);
    if (closeThread.joinable())
    {
        closeThread.detach();
    }

    CPhoneDevice* pThis = this;
    std::thread th([pThis, characteristics, results, photoInfo, osds, path, pByteArrays, ldr, duration, takingTime]()mutable
                   {
                       cv::Mat rgb;
                       std::string cameraInfo;
                       media_status_t mstatus;

                       acamera_metadata_enum_android_lens_facing_t facing = ACAMERA_LENS_FACING_FRONT;
                       ACameraMetadata_const_entry e = { 0 };
                       camera_status_t status = ACameraMetadata_getConstEntry(characteristics.get(), ACAMERA_LENS_FACING, &e);
                       if (status == ACAMERA_OK)
                       {
                           facing = (acamera_metadata_enum_android_lens_facing_t)e.data.u8[0];
                       }

                       int sensorOrientation = 0;
                       {
                           ACameraMetadata_const_entry e = { 0 };
                           status = ACameraMetadata_getConstEntry(characteristics.get(), ACAMERA_SENSOR_ORIENTATION, &e);
                           if (status == ACAMERA_OK)
                           {
                               sensorOrientation = (int)e.data.i32[0];
                           }
                       }

                       if (photoInfo.outputDbgInfo != 0)
                       {
                           if (!results.empty())
                           {
                               NdkCamera::CAPTURE_RESULT captureResult = { 0 };
                               NdkCamera::EnumCameraResult(results[0].get(), captureResult);

                               char extimeunit[4] = { 0 };
                               unsigned int extime = (captureResult.exposureTime >= 1000000) ? ((unsigned int)(captureResult.exposureTime / 1000000)) : ((unsigned int)(captureResult.exposureTime / 1000));
                               strcpy(extimeunit, (captureResult.exposureTime >= 1000000) ? "ms" : "μs");
                               char str[128] = { 0 };
                               snprintf(str, sizeof(str), "AE=%u AF=%u EXPS=%u%s(%d) ISO=%d AFS=%u AES=%u AWBS=%u SCENE=%d LDR=%d(%u) %0.1fx T=%u FD=%lld BURST",
                                        captureResult.autoExposure, captureResult.autoFocus,
                                        extime, extimeunit, captureResult.compensation, captureResult.sensitivity,
                                       // isnan(captureResult.FocusDistance) ? 0 : captureResult.FocusDistance,
                                        (unsigned int)captureResult.afState, (unsigned int)captureResult.aeState, captureResult.awbState,
                                        captureResult.sceneMode, GpioControl::getLightAdc(), ldr, captureResult.zoomRatio,
                                        duration, captureResult.frameDuration);
                               cameraInfo = str;
                           }
                       }

#ifdef OUTPUT_CAMERA_DBG_INFO
#if 0
                       bool shouldRetry = false;
            if (ldr != ~0)
            {
                if (ldr < MIN_LIGHT_Y)
                {
                    if (photoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
                    {
                        shouldRetry = true;
                        char presetBuf[16] = {0};
                        snprintf(presetBuf, sizeof(presetBuf), "%02X", photoInfo.retries);
                        // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(photoInfo.retries) + ".jpg");
                        replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                        XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                              (uint32_t) captureResult.avgY);

                        // photoInfo.usingRawFormat = 1;
                    }
                }
                else if (ldr > MAX_LIGHT_Y)
                {
                    if (photoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
                    {
                        shouldRetry = true;
                        char presetBuf[16] = {0};
                        snprintf(presetBuf, sizeof(presetBuf), "%02X", photoInfo.retries);
                        // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(photoInfo.retries) + ".jpg");
                        replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                        XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                              (uint32_t) captureResult.avgY);
                    }

                    photoInfo.compensation = -2 * ((int16_t) ((uint16_t) captureResult.avgY));
                }
            }
#endif // 0
#endif // OUTPUT_CAMERA_DBG_INFO

                       // Notify to take next photo
                       pThis->TakePhotoCb(1, photoInfo, "", takingTime);

#ifdef USING_EXEC_HDRP
                        if (photoInfo.usingNewHdrplus)
                        {
                            XYLOG(XYLOG_SEVERITY_ERROR, "Start HDR CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);
                            std::vector<std::vector<uint8_t> > localFrames;
                            localFrames.swap(pByteArrays.get()->byteArrays);
#ifdef _DEBUG
                            std::vector<uint8_t>& firstFrame = localFrames[0];
                            writeFile("/sdcard/com.xypower.mpapp/tmp/dngs/1.dng", &firstFrame[0], firstFrame.size());
                            writeFile("/sdcard/com.xypower.mpapp/tmp/dngs/2.dng", &localFrames[1][0], localFrames[1].size());
                            writeFile("/sdcard/com.xypower.mpapp/tmp/dngs/3.dng", &localFrames[2][0], localFrames[2].size());
                            writeFile("/sdcard/com.xypower.mpapp/tmp/dngs/4.dng", &localFrames[3][0], localFrames[3].size());

                            // readFile("/sdcard/com.xypower.mpapp/tmp/dngs/001.dng", localFrames[0]);
                            // readFile("/sdcard/com.xypower.mpapp/tmp/dngs/002.dng", localFrames[1]);
                            // readFile("/sdcard/com.xypower.mpapp/tmp/dngs/003.dng", localFrames[2]);
                            // readFile("/sdcard/com.xypower.mpapp/tmp/dngs/004.dng", localFrames[3]);

#endif
                            doHdrPlus(localFrames, rgb);
                            cv::cvtColor(rgb.clone(), rgb, cv::COLOR_RGB2BGR);

                            localFrames.clear();

#ifdef _DEBUG
                            std::vector<int> params;
                            params.push_back(cv::IMWRITE_JPEG_QUALITY);
                            params.push_back(95);
                            cv::imwrite("/sdcard/com.xypower.mpapp/tmp/1.jpg", rgb, params);
#endif

                            XYLOG(XYLOG_SEVERITY_ERROR, "Finish HDR CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);

                            {
                                cv::Mat tempPic = convert16bit2_8bit_(rgb);
                                rgb = tempPic;
                            }

                            if (photoInfo.orientation > 0)
                            {
                                if (photoInfo.orientation == 1)
                                {
                                    if (facing == ACAMERA_LENS_FACING_FRONT)
                                    {
                                        cv::flip(rgb, rgb, 1);
                                    }
                                }
                                else if (photoInfo.orientation == 2)
                                {
                                    cv::Mat tempPic;
                                    cv::transpose(rgb, tempPic);
                                    cv::flip(tempPic, rgb, 1);
                                }
                                else if (photoInfo.orientation == 3)
                                {
                                    if (facing == ACAMERA_LENS_FACING_FRONT)
                                    {
                                        flip(rgb, rgb, 0);
                                    }
                                    else
                                    {
                                        cv::flip(rgb, rgb, -1);
                                    }
                                }
                                else if ((photoInfo.orientation % 4) == 0)
                                {
                                    cv::Mat tempPic;
                                    cv::transpose(rgb, tempPic);
                                    cv::flip(tempPic, rgb, 0);
                                }

                                XYLOG(XYLOG_SEVERITY_ERROR, "Finish rotation CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);
                            }
                            // cv::cvtColor(rgb, rgb, cv::COLOR_RGB2BGR);
                        }
                        else
                       {
                           uint64_t uniqueId = pThis->m_uniqueIdFeed.fetch_add(1);

                           std::string tmpDir = pThis->m_appPath + (APP_DIR_TMP DIR_SEP_STR) + std::to_string(uniqueId) + DIR_SEP_STR;
                           EnsureDirectoryPathExists(tmpDir);

                           std::vector<std::vector<uint8_t> > localFrames;
                           localFrames.swap(pByteArrays.get()->byteArrays);

                           std::string outputPath = tmpDir + "output.bmp";
                           size_t numberOfFrames = localFrames.size();
                           std::vector<std::string> imagePaths;
                           for (int idx = 0; idx < localFrames.size(); idx++)
                           {
                               std::string imagePath = tmpDir + std::to_string(idx) + ".dng";
                               std::vector<uint8_t>& frame = localFrames[idx];
                               if (writeFile(imagePath, &frame[0], frame.size()))
                               {
                                   imagePaths.push_back(imagePath);
                               }
                           }
                           localFrames.clear();

                           int exitCode = pThis->CallExecv(photoInfo.orientation, facing == ACAMERA_LENS_FACING_FRONT ? 1 : 0, outputPath, imagePaths);
                           for (auto it = imagePaths.cbegin(); it != imagePaths.cend(); ++it)
                           {
                               std::remove((*it).c_str());
                           }

                           if (existsFile(outputPath))
                           {
                               rgb = cv::imread(outputPath);
                               std::remove(outputPath.c_str());
                           }

                           std::error_code errCode;
                           fs::remove_all(fs::path(tmpDir), errCode);
                       }
#else // USING_EXEC_HDRP
                       XYLOG(XYLOG_SEVERITY_ERROR, "Start HDR CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);
                       hdrplus::hdrplus_pipeline pipeline;
                       std::vector<std::vector<uint8_t> > localFrames;
                       localFrames.swap(pByteArrays.get()->byteArrays);
                       pipeline.run_pipeline(localFrames, 0, rgb);
                       localFrames.clear();

                       XYLOG(XYLOG_SEVERITY_ERROR, "Finish HDR CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);

                       {
                           cv::Mat tempPic = convert16bit2_8bit_(rgb);
                           rgb = tempPic;
                       }

                       if (photoInfo.orientation > 0)
                       {
                           if (photoInfo.orientation == 1)
                           {
                               if (facing == ACAMERA_LENS_FACING_FRONT)
                               {
                                   cv::flip(rgb, rgb, 1);
                               }
                           }
                           else if (photoInfo.orientation == 2)
                           {
                               cv::Mat tempPic;
                               cv::transpose(rgb, tempPic);
                               cv::flip(tempPic, rgb, 1);
                           }
                           else if (photoInfo.orientation == 3)
                           {
                               if (facing == ACAMERA_LENS_FACING_FRONT)
                               {
                                   flip(rgb, rgb, 0);
                               }
                               else
                               {
                                   cv::flip(rgb, rgb, -1);
                               }
                           }
                           else if ((photoInfo.orientation % 4) == 0)
                           {
                               cv::Mat tempPic;
                               cv::transpose(rgb, tempPic);
                               cv::flip(tempPic, rgb, 0);
                           }

                           XYLOG(XYLOG_SEVERITY_ERROR, "Finish rotation CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);
                       }
                       cv::cvtColor(rgb, rgb, cv::COLOR_RGB2BGR);
#endif // USING_EXEC_HDRP
                       bool res = pThis->PostProcessPhoto(photoInfo, osds, path, cameraInfo, rgb);
                       if (res)
                       {
                           // TakePhotoCb(2, photoInfo, path, takingTime);
                       }
                   });

    th.detach();

    return true;
}

bool CPhoneDevice::onBurstCapture(std::shared_ptr<ACameraMetadata> characteristics,
                                  std::vector<std::shared_ptr<ACameraMetadata> >& results,
                                  uint32_t ldr, uint32_t duration, std::vector<std::shared_ptr<AImage> >& frames)
{
    time_t takingTime = time(NULL);
    if (mPhotoInfo.remedy != 0)
    {
        if ((takingTime - mPhotoInfo.scheduleTime) > 30)
        {
            takingTime = mPhotoInfo.scheduleTime + mPhotoInfo.channel * 2;
        }
    }
    mPhotoInfo.photoTime = takingTime;

    vector<IDevice::OSD_INFO> osds;
    osds.swap(mOsds);
    PHOTO_INFO photoInfo = mPhotoInfo;
    std::string path;
    path.swap(mPath);

    // std::string tmpPath = m_appPath + std::string(APP_DIR_TMP DIR_SEP_STR) + std::to_string(photoInfo.photoId);

    acamera_metadata_enum_android_lens_facing_t facing = ACAMERA_LENS_FACING_FRONT;
    ACameraMetadata_const_entry e = { 0 };
    camera_status_t status = ACameraMetadata_getConstEntry(characteristics.get(), ACAMERA_LENS_FACING, &e);
    if (status == ACAMERA_OK)
    {
        facing = (acamera_metadata_enum_android_lens_facing_t)e.data.u8[0];
    }

    int sensorOrientation = 0;
    {
        ACameraMetadata_const_entry e = { 0 };
        status = ACameraMetadata_getConstEntry(characteristics.get(), ACAMERA_SENSOR_ORIENTATION, &e);
        if (status == ACAMERA_OK)
        {
            sensorOrientation = (int)e.data.i32[0];
        }
    }

    bool turnOffOtg = (photoInfo.usbCamera != 0);
    CPhoneCamera* pCamera = mCamera;
    mCamera = NULL;

    cv::Mat rgb;
    media_status_t mstatus;

    std::vector<std::shared_ptr<hdrplus::MemFile> > rawFiles;

    if (photoInfo.usingRawFormat != 0)
    {
        for (int idx = 0; idx < frames.size(); idx++)
        {
            std::shared_ptr<AImage> spImage = frames[idx];
            std::shared_ptr<ACameraMetadata> spResult = results[idx];

            hdrplus::MemFile* rawImage = new hdrplus::MemFile();
            rawFiles.push_back(std::shared_ptr<hdrplus::MemFile>(rawImage));
            // rawImage->FromAImage(spImage.get(), characteristics.get(), spResult.get());

            int32_t width = 0;
            int32_t height = 0;
            mstatus = AImage_getWidth(spImage.get(), &width);
            mstatus = AImage_getHeight(spImage.get(), &height);

            int32_t planeCount = 0;
            mstatus = AImage_getNumberOfPlanes(spImage.get(), &planeCount);
            AASSERT(mstatus == AMEDIA_OK && planeCount == 1, "Error: getNumberOfPlanes() planeCount = %d", planeCount);

            uint8_t *planeData = NULL;
            int planeDataLen = 0;
            mstatus = AImage_getPlaneData(spImage.get(), 0, &planeData, &planeDataLen);
            ALOGD("Start Converting Dng");
            DngCreator dngCreator(characteristics.get(), spResult.get());
            dngCreator.writeInputBuffer(rawImage->content, planeData, planeDataLen, width, height, 0);
            ALOGD("End Converting Dng");
        }
    }
    else
    {
        if (results.size() == 1 && frames.size() == 1)
        {
            std::shared_ptr<ACameraMetadata> result = results[0];
            std::shared_ptr<AImage> frame = frames[0];

            int32_t format;
            mstatus = AImage_getFormat(frame.get(), &format);

            if (format == AIMAGE_FORMAT_YUV_420_888)
            {
                int32_t width;
                int32_t height;
                mstatus = AImage_getWidth(frame.get(), &width);
                mstatus = AImage_getHeight(frame.get(), &height);

                int32_t y_pixelStride = 0;
                int32_t u_pixelStride = 0;
                int32_t v_pixelStride = 0;
                AImage_getPlanePixelStride(frame.get(), 0, &y_pixelStride);
                AImage_getPlanePixelStride(frame.get(), 1, &u_pixelStride);
                AImage_getPlanePixelStride(frame.get(), 2, &v_pixelStride);

                int32_t y_rowStride = 0;
                int32_t u_rowStride = 0;
                int32_t v_rowStride = 0;
                AImage_getPlaneRowStride(frame.get(), 0, &y_rowStride);
                AImage_getPlaneRowStride(frame.get(), 1, &u_rowStride);
                AImage_getPlaneRowStride(frame.get(), 2, &v_rowStride);

                uint8_t* y_data = 0;
                uint8_t* u_data = 0;
                uint8_t* v_data = 0;
                int y_len = 0;
                int u_len = 0;
                int v_len = 0;
                AImage_getPlaneData(frame.get(), 0, &y_data, &y_len);
                AImage_getPlaneData(frame.get(), 1, &u_data, &u_len);
                AImage_getPlaneData(frame.get(), 2, &v_data, &v_len);

                if (u_data == v_data + 1 && v_data == y_data + width * height && y_pixelStride == 1 && u_pixelStride == 2 && v_pixelStride == 2 && y_rowStride == width && u_rowStride == width && v_rowStride == width)
                {
                    // already nv21
                    ConvertYUV21ToMat(y_data, width, height, photoInfo.width, photoInfo.height, sensorOrientation, facing == ACAMERA_LENS_FACING_FRONT, photoInfo.orientation, rgb);
                }
                else
                {
                    // construct nv21
                    uint8_t* nv21 = new uint8_t[width * height + width * height / 2];
                    {
                        // Y
                        uint8_t* yptr = nv21;
                        for (int y = 0; y < height; y++)
                        {
                            const uint8_t* y_data_ptr = y_data + y_rowStride * y;
                            for (int x = 0; x < width; x++)
                            {
                                yptr[0] = y_data_ptr[0];
                                yptr++;
                                y_data_ptr += y_pixelStride;
                            }
                        }

                        // UV
                        uint8_t* uvptr = nv21 + width * height;
                        for (int y = 0; y < height / 2; y++)
                        {
                            const uint8_t* v_data_ptr = v_data + v_rowStride * y;
                            const uint8_t* u_data_ptr = u_data + u_rowStride * y;
                            for (int x = 0; x < width / 2; x++)
                            {
                                uvptr[0] = v_data_ptr[0];
                                uvptr[1] = u_data_ptr[0];
                                uvptr += 2;
                                v_data_ptr += v_pixelStride;
                                u_data_ptr += u_pixelStride;
                            }
                        }
                    }

                    ConvertYUV21ToMat(nv21, width, height, photoInfo.width, photoInfo.height, sensorOrientation, facing == ACAMERA_LENS_FACING_FRONT, photoInfo.orientation, rgb);

                    delete[] nv21;
                }
            }
        }
    }

    frames.clear();

    std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, photoInfo.photoId, turnOffOtg);
    m_threadClose.swap(closeThread);
    if (closeThread.joinable())
    {
        closeThread.detach();
    }

    CPhoneDevice* pThis = this;
    std::thread th([pThis, characteristics, results, photoInfo, osds, path, rgb, rawFiles, facing, sensorOrientation, ldr, duration, takingTime]()mutable
        {
            std::string cameraInfo;
            if (photoInfo.outputDbgInfo != 0)
            {
                if (!results.empty())
                {
                    NdkCamera::CAPTURE_RESULT captureResult = { 0 };
                    NdkCamera::EnumCameraResult(results[0].get(), captureResult);

                    char extimeunit[4] = { 0 };
                    unsigned int extime = (captureResult.exposureTime >= 1000000) ? ((unsigned int)(captureResult.exposureTime / 1000000)) : ((unsigned int)(captureResult.exposureTime / 1000));
                    strcpy(extimeunit, (captureResult.exposureTime >= 1000000) ? "ms" : "μs");
                    char str[128] = { 0 };
                    snprintf(str, sizeof(str), "AE=%u AF=%u EXPS=%u%s(%d) ISO=%d AFS=%u AES=%u AWBS=%u SCENE=%d LDR=%d(%u) %0.1fx T=%u FD=%lld",
                             captureResult.autoExposure, captureResult.autoFocus,
                             extime, extimeunit, captureResult.compensation, captureResult.sensitivity,
                            // isnan(captureResult.FocusDistance) ? 0 : captureResult.FocusDistance,
                             (unsigned int)captureResult.afState, (unsigned int)captureResult.aeState, captureResult.awbState,
                             captureResult.sceneMode, GpioControl::getLightAdc(), ldr, captureResult.zoomRatio,
                             duration, captureResult.frameDuration);
                    cameraInfo = str;
                }
            }

#ifdef OUTPUT_CAMERA_DBG_INFO
#if 0
            bool shouldRetry = false;
            if (ldr != ~0)
            {
                if (ldr < MIN_LIGHT_Y)
                {
                    if (photoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
                    {
                        shouldRetry = true;
                        char presetBuf[16] = {0};
                        snprintf(presetBuf, sizeof(presetBuf), "%02X", photoInfo.retries);
                        // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(photoInfo.retries) + ".jpg");
                        replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                        XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                              (uint32_t) captureResult.avgY);

                        // photoInfo.usingRawFormat = 1;
                    }
                }
                else if (ldr > MAX_LIGHT_Y)
                {
                    if (photoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
                    {
                        shouldRetry = true;
                        char presetBuf[16] = {0};
                        snprintf(presetBuf, sizeof(presetBuf), "%02X", photoInfo.retries);
                        // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(photoInfo.retries) + ".jpg");
                        replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                        XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                              (uint32_t) captureResult.avgY);
                    }

                    photoInfo.compensation = -2 * ((int16_t) ((uint16_t) captureResult.avgY));
                }
            }
#endif // 0
#endif // OUTPUT_CAMERA_DBG_INFO

            // Notify to take next photo
            pThis->TakePhotoCb(1, photoInfo, "", takingTime);

            if (photoInfo.usingRawFormat != 0)
            {
#ifndef USING_EXEC_HDRP
                XYLOG(XYLOG_SEVERITY_ERROR, "Start HDR CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);
                hdrplus::hdrplus_pipeline pipeline;
                pipeline.run_pipeline(rawFiles, 0, rgb);
                rawFiles.clear();
#endif

                XYLOG(XYLOG_SEVERITY_ERROR, "Finish HDR CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);

                {
                    cv::Mat tempPic = convert16bit2_8bit_(rgb);
                    rgb = tempPic;
                }

                if (photoInfo.orientation > 0)
                {
                    if (photoInfo.orientation == 1)
                    {
                        if (facing == ACAMERA_LENS_FACING_FRONT)
                        {
                            cv::flip(rgb, rgb, 1);
                        }
                    } else if (photoInfo.orientation == 2)
                    {
                        cv::Mat tempPic;
                        cv::transpose(rgb, tempPic);
                        cv::flip(tempPic, rgb, 1);
                    }
                    else if (photoInfo.orientation == 3)
                    {
                        if (facing == ACAMERA_LENS_FACING_FRONT)
                        {
                            flip(rgb, rgb, 0);
                        }
                        else
                        {
                            cv::flip(rgb, rgb, -1);
                        }
                    }
                    else if (photoInfo.orientation == 4)
                    {
                        cv::Mat tempPic;
                        cv::transpose(rgb, tempPic);
                        cv::flip(tempPic, rgb, 0);
                    }

                    XYLOG(XYLOG_SEVERITY_ERROR, "Finish rotation CH=%u IMGID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.photoId);
                }
                cv::cvtColor(rgb, rgb, cv::COLOR_RGB2BGR);
            }

            bool res = pThis->PostProcessPhoto(photoInfo, osds, path, cameraInfo, rgb);
            if (res)
            {
                // TakePhotoCb(2, photoInfo, path, takingTime);
            }
        });

    th.detach();

    return true;
}

int CPhoneDevice::CallExecv(int rotation, int frontCamera, const std::string& outputPath, const std::vector<std::string>& images)
{
    JNIEnv* env = NULL;
    bool didAttachThread = false;
    bool res = GetJniEnv(m_vm, &env, didAttachThread);
    if (!res)
    {
        ALOGE("Failed to get JNI Env");
    }

    std::string pathsWithSpace;
    for (auto it = images.cbegin(); it != images.cend(); ++it)
    {
        pathsWithSpace.append(*it);
        pathsWithSpace.append(" ");
    }
    pathsWithSpace.pop_back();

    jstring joutputPath = env->NewStringUTF(outputPath.c_str());
    jstring jpathWithSpace = env->NewStringUTF(pathsWithSpace.c_str());
    jint exitCode = env->CallIntMethod(m_javaService, mExecHdrplusMid, rotation, frontCamera, joutputPath, jpathWithSpace);
    env->DeleteLocalRef(jpathWithSpace);
    env->DeleteLocalRef(joutputPath);

    if (didAttachThread)
    {
        m_vm->DetachCurrentThread();
    }

    return exitCode;
}

bool CPhoneDevice::OnImageReady(cv::Mat mat)
{
    time_t takingTime = time(NULL);
    if (mPhotoInfo.remedy != 0)
    {
        if ((takingTime - mPhotoInfo.scheduleTime) > 30)
        {
            takingTime = mPhotoInfo.scheduleTime + mPhotoInfo.channel * 2;
        }
    }
    mPhotoInfo.photoTime = takingTime;
    int baseline = 0;
    cv::Size textSize;
    double height = mat.size().height;
    double width = mat.size().width;
    // double ratio = std::min(height / 1024, width / 1920);
    double ratio = height / 1024.0;
    int thickness = round(1.4 * ratio);
    if (thickness < 1) thickness = 1;
    else if (thickness > 5) thickness = 5;
    cv::Scalar scalarWhite(255, 255, 255);  // white
    int fontSize = (int)(28.0 * ratio);
    cv::Point pt;

    std::string fontPath;
    if (existsFile("/system/fonts/NotoSansCJK-Regular.ttc"))
    {
        fontPath = "/system/fonts/NotoSansCJK-Regular.ttc";
    }
    else if (existsFile("/system/fonts/NotoSerifCJK-Regular.ttc"))
    {
        fontPath = "/system/fonts/NotoSerifCJK-Regular.ttc";
    }
    else
    {
        fontPath = m_appPath+ "fonts/Noto.otf";
    }
    cv::Ptr<cv::ft::FreeType2> ft2;
    ft2 = cv::ft::createFreeType2();
    ft2->loadFontData(fontPath.c_str(), 0);
    // cv::Rect rc(0, 0, mat.cols, mat.rows);
    // cv::rectangle (mat, rc, cv::Scalar(255, 255, 255), cv::FILLED);
    std::vector<IDevice::RECOG_OBJECT> objs;

    if ((m_pRecognizationCfg != NULL) && (m_pRecognizationCfg->enabled != 0) && (mPhotoInfo.recognization != 0))
    {
        XYLOG(XYLOG_SEVERITY_INFO, "Channel AI Enabled");

        // visualize(ncnnPath.c_str(), in);
#ifdef _DEBUG
        double startTime = ncnn::get_current_time();
#endif // _DEBUG

        bool detected = YoloV5NcnnDetect(mat, true, m_pRecognizationCfg->blobName8, m_pRecognizationCfg->blobName16, m_pRecognizationCfg->blobName32, objs);
#ifdef _DEBUG
        double elasped = ncnn::get_current_time() - startTime;
        // __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "%.2fms   detect", elasped);
#endif // _DEBUG
#ifdef _DEBUG
        ALOGI( "NCNN recognization: %.2fms res=%d", elasped, ((detected && !objs.empty()) ? 1 : 0));
#endif
        if (detected && !objs.empty())
        {
#if 0
            static const char* class_names[] = {
                    "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
                    "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
                    "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
                    "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
                    "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
                    "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
                    "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
                    "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
                    "hair drier", "toothbrush"
            };
#endif

            cv::Scalar borderColor(m_pRecognizationCfg->borderColor & 0xFF, (m_pRecognizationCfg->borderColor & 0xFF00) >> 8, (m_pRecognizationCfg->borderColor & 0xFF0000) >> 16);
            cv::Scalar textColor(m_pRecognizationCfg->textColor & 0xFF, (m_pRecognizationCfg->textColor & 0xFF00) >> 8, (m_pRecognizationCfg->textColor & 0xFF0000) >> 16);
            float minSizeW = m_pRecognizationCfg->minSize > 0 ? (mPhotoInfo.width * m_pRecognizationCfg->minSize / 100) : 0;
            float minSizeH = m_pRecognizationCfg->minSize > 0 ? (mPhotoInfo.height * m_pRecognizationCfg->minSize / 100) : 0;

            for (std::vector<IDevice::RECOG_OBJECT>::const_iterator it = objs.cbegin(); it != objs.cend();)
            {
                if (it->label >= m_pRecognizationCfg->items.size())
                {
                    it = objs.erase(it);
                    continue;
                }

                const IDevice::CFG_RECOGNIZATION::ITEM& item = m_pRecognizationCfg->items[it->label];
                if (item.enabled == 0 || it->prob < item.prob)
                {
                    it = objs.erase(it);
                    continue;
                }

                if (m_pRecognizationCfg->minSize > 0)
                {
                    if (it->w < minSizeW || it->h < minSizeH)
                    {
                        it = objs.erase(it);
                        continue;
                    }
                }

                if ((mPhotoInfo.recognization & 0x2) != 0)
                {
                    cv::Rect rc(it->x, it->y, it->w, it->h);
                    cv::rectangle(mat, rc, borderColor, m_pRecognizationCfg->thickness);
                    textSize = ft2->getTextSize(item.name, fontSize, thickness, &baseline);
                    textSize.height += baseline;
                    if (it->y > textSize.height)
                    {
                        pt.y = it->y - textSize.height - 4 - m_pRecognizationCfg->thickness;
                    }
                    else if (mat.rows - it->y - it->h > textSize.height)
                    {
                        pt.y = it->y + it->h + 4 + m_pRecognizationCfg->thickness;
                    }
                    else
                    {
                        // Inner
                        pt.y = it->y + 4 + m_pRecognizationCfg->thickness;
                    }
                    if (mat.cols - it->x > textSize.width)
                    {
                        pt.x = it->x;
                    }
                    else
                    {
                        pt.x = it->x + it->w - textSize.width;
                    }

#ifdef OUTPUT_CAMERA_DBG_INFO
                    char buf[128];
                    snprintf(buf, sizeof(buf), "AI: %d=%s (%f,%f)-(%f,%f) Text:(%d,%d)-(%d,%d)",
                             it->label, item.name.c_str(), it->x, it->y, it->w, it->h, pt.x, pt.y, textSize.width, textSize.height);
                    XYLOG(XYLOG_SEVERITY_DEBUG, buf);
#endif
                    ft2->putText(mat, item.name + std::to_string((int)(it->prob * 100.0)) + "%", pt, fontSize, textColor, thickness, cv::LINE_AA, false, true);
                }
                ++it;
            }
        }
    }
    else
    {
        XYLOG(XYLOG_SEVERITY_WARNING, "Channel AI Disabled");
    }

// #ifdef OUTPUT_CAMERA_DBG_INFO

    if (mCamera != NULL)
    {

        if (mPhotoInfo.outputDbgInfo != 0)
        {
            cv::Scalar scalarRed(0, 0, 255);  // red

            char extimeunit[4] = { 0 };
            char str[128] = { 0 };

            int fs = fontSize * 2 / 3;
            textSize = ft2->getTextSize(str, fs, -1, &baseline);
            cv::Point lt(0, mat.rows - fs - 20 * ratio);
            cv::Point lt2(0, lt.y - 2 * ratio);
            cv::Point rb(0 + textSize.width + 2 * ratio, lt2.y + textSize.height + 8 * ratio);

            if (rb.x > (int)width - 1)
            {
                rb.x = (int)width - 1;
            }
            if (rb.y > (int)height - 1)
            {
                rb.y = (int)height - 1;
            }
            cv::Mat roi = mat(cv::Rect(lt2, rb));
            cv::Mat clrMat(roi.size(), CV_8UC3, scalarWhite);
            double alpha = 0.5;
            cv::addWeighted(clrMat, alpha, roi, 1.0 - alpha, 0.0, roi);

            // cv::rectangle(mat, lt2, rb,cv::Scalar(255, 255, 255), -1);
            ft2->putText(mat, str, lt, fs, scalarRed, -1, cv::LINE_AA, false);
            // DrawOutlineText(ft2, mat, str, cv::Point(0, mat.rows - fs - 20 * ratio), fs, scalarWhite, 1);
        }
    }
// #endif // OUTPUT_CAMERA_DBG_INFO

    for (vector<OSD_INFO>::const_iterator it = mOsds.cbegin(); it != mOsds.cend(); ++it)
    {
        if (it->text.empty())
        {
            continue;
        }

#ifdef _DEBUG
        if (it->alignment == OSD_ALIGNMENT_BOTTOM_RIGHT)
        {
            int aa = 0;
        }
#endif

        textSize = ft2->getTextSize(it->text, fontSize, thickness, &baseline);
        XYLOG(XYLOG_SEVERITY_DEBUG, "%s font Size=%d height: %d baseline=%d", it->text.c_str(), fontSize, textSize.height, baseline);

        if (it->alignment == OSD_ALIGNMENT_TOP_LEFT)
        {
            pt.x = it->x * ratio;
            pt.y = it->y * ratio;
        }
        else if (it->alignment == OSD_ALIGNMENT_TOP_RIGHT)
        {
            pt.x = width - textSize.width - it->x * ratio;
            pt.y= it->y * ratio;
        }
        else if (it->alignment == OSD_ALIGNMENT_BOTTOM_RIGHT)
        {
            pt.x = width - textSize.width - it->x * ratio;
            pt.y = height - it->y * ratio - textSize.height - baseline;
        }
        else if (it->alignment == OSD_ALIGNMENT_BOTTOM_LEFT)
        {
            pt.x = it->x * ratio;
            pt.y = height - it->y * ratio - textSize.height - baseline;
        }

        // cv::Rect rc(pt.x, pt.y, textSize.width, textSize.height);
        // cv::rectangle(mat, rc, cv::Scalar(0,255,255), 2);
        DrawOutlineText(ft2, mat, it->text, pt, fontSize, scalarWhite, thickness);
    }

    std::vector<int> params;
    params.push_back(cv::IMWRITE_JPEG_QUALITY);
    params.push_back((int)((uint32_t)mPhotoInfo.quality));

    bool res = false;
    std::string fullPath = endsWith(mPath, ".jpg") ? mPath : (mPath + CTerminal::BuildPhotoFileName(mPhotoInfo));

#ifdef OUTPUT_CAMERA_DBG_INFO

    bool shouldRetry = false;
#if 0
    if (mCamera != NULL) {
        NdkCamera::CAPTURE_RESULT captureResult = mCamera->getCaptureResult();

        if (captureResult.avgY < MIN_LIGHT_Y)
        {
            if (mPhotoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
            {
                shouldRetry = true;
                char presetBuf[16] = {0};
                snprintf(presetBuf, sizeof(presetBuf), "%02X", mPhotoInfo.retries);
                // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(mPhotoInfo.retries) + ".jpg");
                replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                      (uint32_t) captureResult.avgY);

                // mPhotoInfo.usingRawFormat = 1;
            }
        }
        else if (captureResult.avgY > MAX_LIGHT_Y)
        {
            if (mPhotoInfo.retries < (DEFAULT_TAKE_PHOTO_RETRIES - 1))
            {
                shouldRetry = true;
                char presetBuf[16] = {0};
                snprintf(presetBuf, sizeof(presetBuf), "%02X", mPhotoInfo.retries);
                // replaceAll(fullPath, ".jpg", std::string("-") + std::to_string(mPhotoInfo.retries) + ".jpg");
                replaceAll(fullPath, "_FF_", std::string("_") + presetBuf + std::string("_"));
                XYLOG(XYLOG_SEVERITY_ERROR, "Photo is TOO dark or light(LDR=%u), will RETRY it",
                      (uint32_t) captureResult.avgY);
            }

            mPhotoInfo.compensation = -2 * ((int16_t) ((uint16_t) captureResult.avgY));
        }
    }
#endif

#endif // OUTPUT_CAMERA_DBG_INFO

    if (!std::filesystem::exists(std::filesystem::path(fullPath)))
    {
        bool res = cv::imwrite(fullPath.c_str(), mat, params);
        if (!res)
        {
            XYLOG(XYLOG_SEVERITY_ERROR, "Failed to write photo: %s", fullPath.c_str());
        }
        else
        {
            XYLOG(XYLOG_SEVERITY_INFO, "Succeeded to write photo: %s", fullPath.c_str());
        }
#ifdef OUTPUT_CAMERA_DBG_INFO
        if (shouldRetry)
        {
            TakePhotoCb(0, mPhotoInfo, fullPath, takingTime, objs);
        }
        else
        {
            TakePhotoCb(res ? 3 : 0, mPhotoInfo, fullPath, takingTime, objs);
        }
#else
        TakePhotoCb(res ? 3 : 0, mPhotoInfo, fullPath, takingTime, objs);
#endif
    }
    else
    {
        ALOGI("Photo file exists: %s", mPath.c_str());
    }

    return res;
}

bool CPhoneDevice::PostProcessPhoto(const PHOTO_INFO& photoInfo, const vector<IDevice::OSD_INFO>& osds, const std::string& path, const std::string& cameraInfo, cv::Mat mat)
{
    int baseline = 0;
    cv::Size textSize;
    double height = mat.rows;
    double width = mat.cols;
    // double ratio = std::min(height / 1024, width / 1920);
    double ratio = height / 1024.0;
    int thickness = round(1.4 * ratio);
    if (thickness < 1) thickness = 1;
    else if (thickness > 5) thickness = 5;
    cv::Scalar scalarWhite(255, 255, 255);  // white
    int fontSize = (int)(28.0 * ratio);
    cv::Point pt;

    std::string fontPath;
    if (existsFile("/system/fonts/NotoSansCJK-Regular.ttc"))
    {
        fontPath = "/system/fonts/NotoSansCJK-Regular.ttc";
    }
    else if (existsFile("/system/fonts/NotoSerifCJK-Regular.ttc"))
    {
        fontPath = "/system/fonts/NotoSerifCJK-Regular.ttc";
    }
    else
    {
        fontPath = m_appPath+ "fonts/Noto.otf";
    }
    cv::Ptr<cv::ft::FreeType2> ft2;
    ft2 = cv::ft::createFreeType2();
    ft2->loadFontData(fontPath.c_str(), 0);
    // cv::Rect rc(0, 0, mat.cols, mat.rows);
    // cv::rectangle (mat, rc, cv::Scalar(255, 255, 255), cv::FILLED);
    std::vector<IDevice::RECOG_OBJECT> objs;

    if ((m_pRecognizationCfg != NULL) && (m_pRecognizationCfg->enabled != 0) && (photoInfo.recognization != 0))
    {
        XYLOG(XYLOG_SEVERITY_INFO, "Channel AI Enabled");

        // visualize(ncnnPath.c_str(), in);
#ifdef _DEBUG
        double startTime = ncnn::get_current_time();
#endif // _DEBUG

        bool detected = YoloV5NcnnDetect(mat, true, m_pRecognizationCfg->blobName8, m_pRecognizationCfg->blobName16, m_pRecognizationCfg->blobName32, objs);
#ifdef _DEBUG
        double elasped = ncnn::get_current_time() - startTime;
        // __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "%.2fms   detect", elasped);
#endif // _DEBUG
#ifdef _DEBUG
        ALOGI( "NCNN recognization: %.2fms res=%d", elasped, ((detected && !objs.empty()) ? 1 : 0));
#endif
        if (detected && !objs.empty())
        {
            cv::Scalar borderColor(m_pRecognizationCfg->borderColor & 0xFF, (m_pRecognizationCfg->borderColor & 0xFF00) >> 8, (m_pRecognizationCfg->borderColor & 0xFF0000) >> 16);
            cv::Scalar textColor(m_pRecognizationCfg->textColor & 0xFF, (m_pRecognizationCfg->textColor & 0xFF00) >> 8, (m_pRecognizationCfg->textColor & 0xFF0000) >> 16);
            float minSizeW = m_pRecognizationCfg->minSize > 0 ? (photoInfo.width * m_pRecognizationCfg->minSize / 100) : 0;
            float minSizeH = m_pRecognizationCfg->minSize > 0 ? (photoInfo.height * m_pRecognizationCfg->minSize / 100) : 0;

            for (std::vector<IDevice::RECOG_OBJECT>::const_iterator it = objs.cbegin(); it != objs.cend();)
            {
                if (it->label >= m_pRecognizationCfg->items.size())
                {
                    it = objs.erase(it);
                    continue;
                }

                const IDevice::CFG_RECOGNIZATION::ITEM& item = m_pRecognizationCfg->items[it->label];
                if (item.enabled == 0 || it->prob < item.prob)
                {
                    it = objs.erase(it);
                    continue;
                }

                if (m_pRecognizationCfg->minSize > 0)
                {
                    if (it->w < minSizeW || it->h < minSizeH)
                    {
                        it = objs.erase(it);
                        continue;
                    }
                }

                if ((photoInfo.recognization & 0x2) != 0)
                {
                    cv::Rect rc(it->x, it->y, it->w, it->h);
                    cv::rectangle(mat, rc, borderColor, m_pRecognizationCfg->thickness);
                    textSize = ft2->getTextSize(item.name, fontSize, thickness, &baseline);
                    textSize.height += baseline;
                    if (it->y > textSize.height)
                    {
                        pt.y = it->y - textSize.height - 4 - m_pRecognizationCfg->thickness;
                    }
                    else if (mat.rows - it->y - it->h > textSize.height)
                    {
                        pt.y = it->y + it->h + 4 + m_pRecognizationCfg->thickness;
                    }
                    else
                    {
                        // Inner
                        pt.y = it->y + 4 + m_pRecognizationCfg->thickness;
                    }
                    if (mat.cols - it->x > textSize.width)
                    {
                        pt.x = it->x;
                    }
                    else
                    {
                        pt.x = it->x + it->w - textSize.width;
                    }

#ifdef OUTPUT_CAMERA_DBG_INFO
                    char buf[128];
                    snprintf(buf, sizeof(buf), "AI: %d=%s (%f,%f)-(%f,%f) Text:(%d,%d)-(%d,%d)",
                             it->label, item.name.c_str(), it->x, it->y, it->w, it->h, pt.x, pt.y, textSize.width, textSize.height);
                    XYLOG(XYLOG_SEVERITY_DEBUG, buf);
#endif
                    ft2->putText(mat, item.name + std::to_string((int)(it->prob * 100.0)) + "%", pt, fontSize, textColor, thickness, cv::LINE_AA, false, true);
                }
                ++it;
            }
        }
    }
    else
    {
        XYLOG(XYLOG_SEVERITY_WARNING, "Channel AI Disabled");
    }

// #ifdef OUTPUT_CAMERA_DBG_INFO

    if (!cameraInfo.empty())
    {
        // NdkCamera::CAPTURE_RESULT captureResult = mCamera->getCaptureResult();

        if (photoInfo.outputDbgInfo != 0)
        {
            cv::Scalar scalarRed(0, 0, 255);  // red

            int fs = fontSize * 2 / 3;
            textSize = ft2->getTextSize(cameraInfo, fs, -1, &baseline);
            cv::Point lt(0, mat.rows - fs - 20 * ratio);
            cv::Point lt2(0, lt.y - 2 * ratio);
            cv::Point rb(0 + textSize.width + 2 * ratio, lt2.y + textSize.height + 8 * ratio);

            if (rb.x > (int)width - 1)
            {
                rb.x = (int)width - 1;
            }
            if (rb.y > (int)height - 1)
            {
                rb.y = (int)height - 1;
            }
            cv::Mat roi = mat(cv::Rect(lt2, rb));
            cv::Mat clrMat(roi.size(), CV_8UC3, scalarWhite);
            double alpha = 0.5;
            cv::addWeighted(clrMat, alpha, roi, 1.0 - alpha, 0.0, roi);

            // cv::rectangle(mat, lt2, rb,cv::Scalar(255, 255, 255), -1);
            ft2->putText(mat, cameraInfo, lt, fs, scalarRed, -1, cv::LINE_AA, false);

            // DrawOutlineText(ft2, mat, str, cv::Point(0, mat.rows - fs - 20 * ratio), fs, scalarWhite, 1);
        }
    }
// #endif // OUTPUT_CAMERA_DBG_INFO

    for (vector<OSD_INFO>::const_iterator it = osds.cbegin(); it != osds.cend(); ++it)
    {
        if (it->text.empty())
        {
            continue;
        }

#ifdef _DEBUG
        if (it->alignment == OSD_ALIGNMENT_BOTTOM_RIGHT)
        {
            int aa = 0;
        }
#endif

        textSize = ft2->getTextSize(it->text, fontSize, thickness, &baseline);
        XYLOG(XYLOG_SEVERITY_DEBUG, "%s font Size=%d height: %d baseline=%d", it->text.c_str(), fontSize, textSize.height, baseline);

        if (it->alignment == OSD_ALIGNMENT_TOP_LEFT)
        {
            pt.x = it->x * ratio;
            pt.y = it->y * ratio;
        }
        else if (it->alignment == OSD_ALIGNMENT_TOP_RIGHT)
        {
            pt.x = width - textSize.width - it->x * ratio;
            pt.y= it->y * ratio;
        }
        else if (it->alignment == OSD_ALIGNMENT_BOTTOM_RIGHT)
        {
            pt.x = width - textSize.width - it->x * ratio;
            pt.y = height - it->y * ratio - textSize.height - baseline;
        }
        else if (it->alignment == OSD_ALIGNMENT_BOTTOM_LEFT)
        {
            pt.x = it->x * ratio;
            pt.y = height - it->y * ratio - textSize.height - baseline;
        }

        // cv::Rect rc(pt.x, pt.y, textSize.width, textSize.height);
        // cv::rectangle(mat, rc, cv::Scalar(0,255,255), 2);
        DrawOutlineText(ft2, mat, it->text, pt, fontSize, scalarWhite, thickness);
    }

    std::vector<int> params;
    params.push_back(cv::IMWRITE_JPEG_QUALITY);
    params.push_back((int)((uint32_t)photoInfo.quality));

    bool res = false;
    std::string fullPath = endsWith(path, ".jpg") ? path : (path + CTerminal::BuildPhotoFileName(photoInfo));

    if (!std::filesystem::exists(std::filesystem::path(fullPath)))
    {
#ifdef _DEBUG
        char log[256] = { 0 };
        strcpy(log, fullPath.c_str());
#endif
        bool res = cv::imwrite(fullPath.c_str(), mat, params);
#ifdef _DEBUG
        if (getFileSize(fullPath.c_str()) == 0)
        {
            int aa = 0;
        }
#endif
        if (!res)
        {
            XYLOG(XYLOG_SEVERITY_ERROR, "Failed to Write File: %s", fullPath.c_str() + m_appPath.size());
        }
        else
        {
            XYLOG(XYLOG_SEVERITY_INFO, "Succeeded to Write File: %s", fullPath.c_str() + m_appPath.size());
        }
        TakePhotoCb(res ? 2 : 0, photoInfo, fullPath, photoInfo.photoTime, objs);
    }
    else
    {
        XYLOG(XYLOG_SEVERITY_INFO, "Photo File Exists: %s", fullPath.c_str() + m_appPath.size());
    }

    return res;
}

bool CPhoneDevice::OnCaptureReady(bool photoOrVideo, bool result, cv::Mat mat, unsigned int photoId)
{
    XYLOG(XYLOG_SEVERITY_INFO, "RAW Capture finished: %u RES=%d", photoId, (result ? 1 : 0));
    if (photoOrVideo)
    {
        if (result)
        {
            OnImageReady(mat);
        }
        else
        {
            std::vector<IDevice::RECOG_OBJECT> objs;
            TakePhotoCb(0, mPhotoInfo, "", time(NULL), objs);

            CPhoneCamera* pCamera = mCamera;
            mCamera = NULL;

            bool turnOffOtg = (mPhotoInfo.usbCamera != 0);
            std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, mPhotoInfo.photoId, turnOffOtg);
            m_threadClose.swap(closeThread);
            if (closeThread.joinable())
            {
                closeThread.detach();
            }
        }
    }

    return true;
}

bool CPhoneDevice::OnVideoReady(bool photoOrVideo, bool result, const char* path, unsigned int photoId)
{
    if (photoOrVideo)
    {
        mPhotoInfo.photoTime = time(NULL);
        CPhoneCamera* pCamera = NULL;

        std::vector<IDevice::RECOG_OBJECT> objs;
        std::string fullPath = mPath + CTerminal::BuildPhotoFileName(mPhotoInfo);
        if (result)
        {
            std::rename(path, fullPath.c_str());
        }
        TakePhotoCb(result ? 3 : 0, mPhotoInfo, fullPath, time(NULL), objs);

        bool turnOffOtg = (mPhotoInfo.usbCamera != 0);
        std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, mPhotoInfo.photoId, turnOffOtg);
        m_threadClose.swap(closeThread);
    }
    else
    {
        mPhotoInfo.photoTime = time(NULL);
        CPhoneCamera* pCamera = NULL;

        std::vector<IDevice::RECOG_OBJECT> objs;
        std::string fullPath = mPath + CTerminal::BuildPhotoFileName(mPhotoInfo);
        if (result)
        {
            std::rename(path, fullPath.c_str());
        }
        TakePhotoCb(result ? 3 : 0, mPhotoInfo, fullPath, time(NULL), objs);

        bool turnOffOtg = (mPhotoInfo.usbCamera != 0);
        std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, mPhotoInfo.photoId, turnOffOtg);
        m_threadClose.swap(closeThread);
    }

    return result;
}

void CPhoneDevice::onError(const std::string& msg)
{
    if (mCamera == NULL)
    {
        int aa = 0;
        return;
    }
    XYLOG(XYLOG_SEVERITY_ERROR, "Failed to Take Photo (IMGID=%u): %s", mPhotoInfo.photoId, msg.c_str());

    CPhoneCamera* pCamera = mCamera;
    mCamera = NULL;

    TakePhotoCb(0, mPhotoInfo, mPath, 0);

    bool turnOffOtg = (mPhotoInfo.usbCamera != 0);
    std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, mPhotoInfo.photoId, turnOffOtg);
    // closeThread.detach();
    m_threadClose.swap(closeThread);
}

void CPhoneDevice::onDisconnected(ACameraDevice* device)
{
    if (mCamera == NULL)
    {
        return;
    }
    XYLOG(XYLOG_SEVERITY_ERROR, "Failed to Take Photo (IMGID=%u) as for Disconnection", mPhotoInfo.photoId);

    CPhoneCamera* pCamera = mCamera;
    mCamera = NULL;

    TakePhotoCb(0, mPhotoInfo, mPath, 0);

    bool turnOffOtg = (mPhotoInfo.usbCamera != 0);
    std::thread closeThread(&CPhoneDevice::CloseCamera2, this, pCamera, mPhotoInfo.photoId, turnOffOtg);
    // closeThread.detach();
    m_threadClose.swap(closeThread);
}

std::string CPhoneDevice::GetFileName() const
{
    return mPath;
}

std::string CPhoneDevice::GetVersion() const
{
    // FOR OSD
    string version = std::to_string(mVersionCode / 100000);
    version += ".";
    version += std::to_string((mVersionCode % 100000) / 1000);
    version += ".";
    version += std::to_string(mVersionCode % 1000);

    return version;
}

void CPhoneDevice::UpdatePosition(double lon, double lat, double radius, time_t ts)
{
    if (m_listener != NULL)
    {
        if (shouldConvertPosition(lat, lon))
        {
            transformPosition(lat, lon);
        }
        return m_listener->OnPositionDataArrived(lon, lat, radius, ts);
    }
}

void CPhoneDevice::TurnOnCameraPower(JNIEnv* env)
{
    m_powerLocker.lock();
    if (mCameraPowerCount == 0)
    {
        GpioControl::setCam3V3Enable(true);
    }
    mCameraPowerCount++;
    m_powerLocker.unlock();
}

void CPhoneDevice::TurnOffCameraPower(JNIEnv* env)
{
    bool turnedOff = false;
    m_powerLocker.lock();
    if (mCameraPowerCount > 0)
    {
        mCameraPowerCount--;
        if (mCameraPowerCount == 0)
        {
            GpioControl::setCam3V3Enable(false);
            turnedOff = true;
        }
    }
    m_powerLocker.unlock();

    if (turnedOff)
    {
        XYLOG(XYLOG_SEVERITY_INFO, "CAM PWR Turned Off");
    }
}

void CPhoneDevice::TurnOnOtg(JNIEnv* env)
{
    m_powerLocker.lock();
    if (mOtgCount == 0)
    {
        ALOGD("setOtgState 1");
        GpioControl::setOtgState(true);
    }
    mOtgCount++;
    m_powerLocker.unlock();
}

void CPhoneDevice::TurnOffOtg(JNIEnv* env)
{
    bool turnedOff = false;
    m_powerLocker.lock();
    if (mOtgCount > 0)
    {
        mOtgCount--;
        if (mOtgCount == 0)
        {
            // ALOGD("setOtgState 0");
            GpioControl::setOtgState(false);
            turnedOff = true;
        }
    }
    m_powerLocker.unlock();

    if (turnedOff)
    {
        XYLOG(XYLOG_SEVERITY_INFO, "OTG PWR Turned Off");
    }
}

void CPhoneDevice::UpdateSignalLevel(int signalLevel)
{
    m_signalLevel = signalLevel;
    m_signalLevelUpdateTime = time(NULL);
}

void CPhoneDevice::UpdateSimcard(const std::string& simcard)
{
    m_simcard = simcard;
}

int CPhoneDevice::GetIceData(IDevice::ICE_INFO *iceInfo, IDevice::ICE_TAIL *iceTail, SENSOR_PARAM sensorParam)
{
    Collect_sensor_data();		//15s
    Data_DEF airt;
    //++等值覆冰厚度, 综合悬挂载荷, 不均衡张力差 置0
    iceInfo->equal_icethickness = 0;
    iceInfo->tension = 0;
    iceInfo->tension_difference = 0;

    GetPullValue(2, &airt);
    iceInfo->t_sensor_data[0].original_tension = airt.EuValue;
    GetPullValue(4, &airt);
    iceInfo->t_sensor_data[1].original_tension = airt.EuValue;
    GetPullValue(5, &airt);
    iceInfo->t_sensor_data[2].original_tension = airt.EuValue;


    GetAngleValue(6, &airt, 0);
    iceInfo->t_sensor_data[0].deflection_angle = airt.EuValue;
    GetAngleValue(6, &airt, 1);
    iceInfo->t_sensor_data[0].windage_yaw_angle = airt.EuValue;
    GetAngleValue(7, &airt, 0);
    iceInfo->t_sensor_data[1].deflection_angle = airt.EuValue;
    GetAngleValue(7, &airt, 1);
    iceInfo->t_sensor_data[1].windage_yaw_angle = airt.EuValue;
    GetAngleValue(13, &airt, 0);
    iceInfo->t_sensor_data[2].deflection_angle = airt.EuValue;
    GetAngleValue(13, &airt, 1);
    iceInfo->t_sensor_data[2].windage_yaw_angle = airt.EuValue;

    GetWindSpeedData(&airt);
    iceTail->instantaneous_windspeed = airt.EuValue;
    GetWindDirectionData(&airt);
    iceTail->instantaneous_winddirection = airt.EuValue;//需求无符号整数给出浮点数
    GetAirTempData(&airt);
    iceTail->air_temperature = airt.EuValue;
    GetHumidityData(&airt);
    iceTail->humidity = airt.EuValue;//需求无符号整数给出浮点数

    return true;
}



int CPhoneDevice::GetWData(IDevice::WEATHER_INFO *weatherInfo)
{
    Collect_sensor_data();		//15s

    Data_DEF airt;
    GetWeatherData(&airt, 0);
    weatherInfo->air_temperature = airt.EuValue;

    if (airt.AiState == -1) return false;

    GetWeatherData(&airt, 1);
    weatherInfo->humidity = airt.EuValue;
    GetWeatherData(&airt, 2);
    weatherInfo->avg_windspeed_10min = airt.EuValue;
    weatherInfo->extreme_windspeed = airt.EuValue;
    weatherInfo->standard_windspeed = airt.EuValue;
    GetWeatherData(&airt, 3);
    weatherInfo->avg_winddirection_10min = airt.EuValue;
    GetWeatherData(&airt, 4);
    weatherInfo->precipitation = airt.EuValue;
    GetWeatherData(&airt, 5);
    weatherInfo->air_pressure = airt.EuValue;
    GetWeatherData(&airt, 6);
    weatherInfo->radiation_intensity = airt.EuValue;
    return true;

}

#ifdef USING_N938
bool CPhoneDevice::OpenSensors()
{
    GpioControl::setInt(CMD_SET_CAM_3V3_EN_STATE, true ? 1 : 0);
    GpioControl::setInt(CMD_SET_485_EN_STATE, true ? 1 : 0);
    int     igpio;
    GpioControl::setInt(CMD_SET_WTH_POWER, 1);
    GpioControl::setInt(CMD_SET_PULL_POWER, 1);
    GpioControl::setInt(CMD_SET_ANGLE_POWER, 1);
    GpioControl::setInt(CMD_SET_OTHER_POWER, 1);
    GpioControl::setInt(CMD_SET_PIC1_POWER, 1);
}

bool CPhoneDevice::CloseSensors()
{
    return false;
}

#else
bool CPhoneDevice::OpenSensors()
{
    return false;
}

bool CPhoneDevice::CloseSensors()
{
    return false;
}

#endif
