//
// Created by Fear1ess on 2021/4/2.
//

#include "collect.h"
#include "wd_jni.h"
#include "utils.h"
#include <stdlib.h>
#include "wd_result.h"
#include "wdun.h"
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/sysinfo.h>
#include <sys/statfs.h>
#include "openssl/md5.h"
#include <arpa/inet.h>
#include <sys/syscall.h>
#include <linux/prctl.h>

#define WD_COLLECT "wd_collect"

extern jobject g_app_context;
extern struct wd_funcs g_funcs;


IMPORTWDSYSCALL

cJSON *collect_init() {
    return cJSON_CreateObject();
}

void do_collect(JNIEnv* env) {
    cJSON* json =  collect_init();
    struct timespec ts = {0};
    double start_time = 0, end_time = 0;
    if(WDSYSCALL(SYS_clock_gettime, CLOCK_MONOTONIC, &ts) == 0) {
        start_time = ts.tv_nsec / 1000000  + ts.tv_sec * 1000ULL;
    }
    collect_app_info(env, json);
    int permissions = collect_permissions(env, json);
    collect_imei(env, json);
    collect_android_id(env, json);
    collect_mac_addr(env, json);
    collect_build_info(env, json);
    collect_prop_info(env, json);
    collect_settings_info(env, json);
    collect_display_info(env, json);
    collect_proxy_info(env, json);
    collect_camera_info(env, json);
    collect_battery_info(env, json);
    collect_env(env, json);
    collect_libs_info(env, json);
    collect_cpu_info(env, json);
    collect_sensor_info(env, json);
    collect_mem_info(env, json);
    collect_network_info(env, json);
    collect_user_agent(env, json);
    if((permissions & PERMISSION_ACCESS_FINE_LOCATION) && (permissions & PERMISSION_ACCESS_COARSE_LOCATION)) {
        collect_location_info(env, json);
    }
    if((permissions & PERMISSION_READ_PHONE_STATE) && (permissions & PERMISSION_ACCESS_COARSE_LOCATION)) {
        collect_cell_info(env, json);
    }

    if(WDSYSCALL(SYS_clock_gettime, CLOCK_MONOTONIC, &ts) == 0) {
        end_time = (double)ts.tv_nsec / 1000000  + (double)ts.tv_sec * 1000ULL;
    }

    char srt[32] = {0};
    snprintf(srt, 31, "%.2lf", end_time - start_time);
    cJSON_AddStringToObject(json, "srt", srt);

    const char* data = cJSON_Print(json);

    size_t len = strlen(data);
    int fd = WDSYSCALL(SYS_openat, AT_FDCWD, "/sdcard/wd_data", O_CREAT|O_RDWR|O_TRUNC, S_IRWXU);
    WDSYSCALL(SYS_write, fd, data, len);
    WDSYSCALL(SYS_close, fd);
}

void collect_app_info(JNIEnv* env, cJSON *json) {
    if((*env)->PushLocalFrame(env, 20) != JNI_OK) {
        loge(WD_COLLECT, "%s", "collect_app_info func call env->pushLocalFrame failed!");
        return;
    }
    //package_name
    jobject pkgName = wdCallObjectMethod(env, g_app_context, "getPackageName", "()Ljava/lang/String;");
    addJniStringToJson(env, json, "package_name", pkgName);

    //get packagemanager and pkginfo
    jobject pkgManager = wdCallObjectMethod(env, g_app_context, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject pkgInfo = wdCallObjectMethod(env, pkgManager, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", pkgName, 0x40);


    //last update time
    jlong lastUpdateTime = wdGetLongField(env, pkgInfo, "lastUpdateTime", "J");
    cJSON_AddNumberToObject(json, "last_update_time", lastUpdateTime);

    //first install time
    jlong firstInstallTime = wdGetLongField(env, pkgInfo, "firstInstallTime", "J");
    cJSON_AddNumberToObject(json, "first_install_time", firstInstallTime);

    //version code
    jint versionCode = wdGetIntField(env, pkgInfo, "versionCode", "I");
    cJSON_AddNumberToObject(json, "ver_code", versionCode);

    //version name
    jobject versionName = wdGetObjectField(env, pkgInfo, "versionName", "Ljava/lang/String;");
    addJniStringToJson(env, json, "versionName", versionName);

    //signature's sha256
    jobject signatures = wdGetObjectField(env, pkgInfo, "signatures", "[Landroid/content/pm/Signature;");
    jobject sig_item0 = (*env)->GetObjectArrayElement(env, signatures, 0);
    jobject signature = wdCallObjectMethod(env, sig_item0, "toCharsString", "()Ljava/lang/String;");
    char* sha256 = (char*)malloc(64 + 1);
    getJniStringSha256(env, signature, sha256);
    cJSON_AddStringToObject(json, "signature_sha256", sha256);

    //app label
    jobject aiInfo = wdCallObjectMethod(env, g_app_context, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
    jobject label = wdCallObjectMethod(env, pkgManager, "getApplicationLabel", "(Landroid/content/pm/ApplicationInfo;)Ljava/lang/CharSequence;", aiInfo);
    addJniStringToJson(env, json, "app_label", label);

    //app_debug & app_backup
    jint flags = wdGetIntField(env, aiInfo, "flags", "I");
    cJSON_AddNumberToObject(json, "app_debug", (flags & (1 << 1)) ? 1 : 0);
    cJSON_AddNumberToObject(json, "app_backup", (flags & (1 << 15)) ? 1 : 0);

    logd(WD_COLLECT, "%s", "collect app info finished...");

    (*env)->PopLocalFrame(env, NULL);
}

int collect_permissions(JNIEnv* env, cJSON* json) {

    int value = 0;
    const char* permissions[] = {
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.WRITE_EXTERNAL_STORAGE",
            "android.permission.READ_PHONE_STATE",
            "android.permission.ACCESS_NETWORK_STATE",
            "android.permission.ACCESS_WIFI_STATE",
            "android.permission.ACCESS_COARSE_LOCATION",
            "android.permission.ACCESS_FINE_LOCATION"
    };
    for(int i = 0; i < sizeof(permissions)/sizeof(const char*); ++i) {
        jstring str = (*env)->NewStringUTF(env, permissions[i]);
        if(wdCallIntMethod(env, g_app_context, "checkSelfPermission", "(Ljava/lang/String;)I", str) == 0) {
            value |= (1 << i);
        }
        (*env)->DeleteLocalRef(env, str);
    }

    return value;
}

void collect_imei(JNIEnv *env, cJSON *json) {
    //get imei from telephonymanager
    jstring phone_str = (*env)->NewStringUTF(env, "phone");
    jobject tm = wdCallObjectMethod(env, g_app_context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", phone_str);
    jobject imei_str = wdCallObjectMethod(env, tm, "getDeviceId", "()Ljava/lang/String;");
    addJniStringToJson(env, json, "imei", imei_str);
    (*env)->DeleteLocalRef(env, phone_str);
    (*env)->DeleteLocalRef(env, tm);
    (*env)->DeleteLocalRef(env, imei_str);

    logd(WD_COLLECT, "%s", "collect imei finished...");
}



void collect_android_id(JNIEnv* env, cJSON* json) {
    jstring key_str = (*env)->NewStringUTF(env, "android_id");
    jobject cr = wdCallObjectMethod(env, g_app_context, "getContentResolver", "()Landroid/content/ContentResolver;");
    jobject androidId_jstr = wdCallStaticObjectMethod(env, "android/provider/Settings$Secure", "getString",
            "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;", cr, key_str);
    addJniStringToJson(env, json, "android_id", androidId_jstr);
    (*env)->DeleteLocalRef(env, key_str);
    (*env)->DeleteLocalRef(env, cr);
    (*env)->DeleteLocalRef(env, androidId_jstr);
    logd(WD_COLLECT, "%s", "collect android_id finished...");
}

void collect_mac_addr(JNIEnv *env, cJSON *json) {
    // //above android 11.0+ can't get mac by this method
    jstring name_str = (*env)->NewStringUTF(env, "wlan0");
    jobject ni = wdCallStaticObjectMethod(env, "java/net/NetworkInterface", "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;", name_str);
    jbyteArray mac_byteArr = wdCallObjectMethod(env, ni, "getHardwareAddress", "()[B");
    if(mac_byteArr == NULL) {
        cJSON_AddStringToObject(json, "mac_addr", "");
        goto return_label;
    }
    jsize size = (*env)->GetArrayLength(env, mac_byteArr);
    jbyte* bytes = (*env)->GetByteArrayElements(env, mac_byteArr, NULL);
    char* out = (char*) malloc(size * 3);
    bytes2Hex((const uint8_t*)bytes, out, size, 1);
    cJSON_AddStringToObject(json, "mac_addr", out);
    free(out);
    (*env)->ReleaseByteArrayElements(env, mac_byteArr, bytes, 0);
    (*env)->DeleteLocalRef(env, mac_byteArr);
    return_label:
    (*env)->DeleteLocalRef(env, ni);
    (*env)->DeleteLocalRef(env, name_str);

    char* mac_addr2 = "";
    int fd = WDSYSCALL(SYS_openat, AT_FDCWD, "/sys/class/net/wlan0/address", O_RDONLY, NULL);
    if(fd > 0) {
        char buf[32] = {0};
        if(WDSYSCALL(SYS_read, fd, buf, 32) > 0) {
            mac_addr2 = buf;
        }
    }
    cJSON_AddStringToObject(json, "mac_addr2", mac_addr2);

    logd(WD_COLLECT, "%s", "collect mac_addr finished...");
}

void collect_build_info(JNIEnv *env, cJSON *json) {
    if((*env)->PushLocalFrame(env, 20) != JNI_OK) {
        loge(WD_COLLECT, "%s", "collect_build_info func call env->pushLocalFrame failed!");
        return;
    }
    const char* build_key[] = {"SERIAL", "BOARD", "BOOTLOADER", "CPU_ABI", "CPU_ABI2", "DEVICE", "DISPLAY", "FINGERPRINT", "HARDWARE",
                               "HOST", "ID", "MANUFACTURER", "MODEL", "PRODUCT", "BRAND"};
    cJSON* bi_json = cJSON_CreateObject();

    for(int i = 0; i < sizeof(build_key)/sizeof(const char*); ++i) {
        jobject jstr = wdGetStaticObjectField(env, "android/os/Build", build_key[i], "Ljava/lang/String;");
        addJniStringToJson(env, bi_json, build_key[i], jstr);
    }

    cJSON_AddItemToObject(json, "build", bi_json);
    (*env)->PopLocalFrame(env, NULL);

    logd(WD_COLLECT, "%s", "collect build_info finished...");
}

void collect_prop_info(JNIEnv* env, cJSON* json) {
    if(g_funcs.wd_system_property_get == NULL) return;
    cJSON* pi_json = cJSON_CreateObject();
    const char* prop_key[] = {"wifi.interface", "gsm.sim.state", "gsm.version.baseband", "gsm.version.ril-impl",
                              "gsm.current.phone-type", "gsm.operator.isroaming", "gsm.network.type", "persist.sys.timezone",
                              "init.svc.adbd", "ro.build.date.utc", "gsm.operator.alpha", "ro.opengles.version"};
    char buf[64];
    for(int i = 0; i < sizeof(prop_key)/sizeof(const char*); ++i) {
        memset((void*)buf, 0, 64);
        g_funcs.wd_system_property_get(prop_key[i], buf);
        cJSON_AddStringToObject(pi_json, prop_key[i], buf);
    }
    cJSON_AddItemToObject(json, "prop", pi_json);
    logd(WD_COLLECT, "%s", "collect prop_info finished...");
}

void collect_settings_info(JNIEnv *env, cJSON *json) {
    const char* settings_secure_key[] = {"usb_mass_storage_enabled", "development_settings_enabled", "lock_pattern_autolock",
                                       "lock_pattern_visible_pattern"};
    const char* settings_system_key[] = {"screen_brightness", "accelerometer_rotation", "screen_brightness_mode",
                                       "sound_effects_enabled", "screen_off_timeout"};

    cJSON* si_json = cJSON_CreateObject();
    for(int i = 0; i < sizeof(settings_secure_key)/sizeof(const char*); ++i) {
        jstring key_str = (*env)->NewStringUTF(env, settings_secure_key[i]);
        jobject cr = wdCallObjectMethod(env, g_app_context, "getContentResolver", "()Landroid/content/ContentResolver;");
        jobject jstr = wdCallStaticObjectMethod(env, "android/provider/Settings$Secure", "getString",
                                                          "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;", cr, key_str);
        addJniStringToJson(env, si_json, settings_secure_key[i], jstr);
        (*env)->DeleteLocalRef(env, key_str);
        (*env)->DeleteLocalRef(env, cr);
        (*env)->DeleteLocalRef(env, jstr);
    }
    for(int j = 0; j < sizeof(settings_system_key)/sizeof(const char*); ++j) {
        jstring key_str = (*env)->NewStringUTF(env, settings_system_key[j]);
        jobject cr = wdCallObjectMethod(env, g_app_context, "getContentResolver", "()Landroid/content/ContentResolver;");
        jint value = wdCallStaticIntMethod(env, "android/provider/Settings$System", "getInt",
                                                "(Landroid/content/ContentResolver;Ljava/lang/String;)I", cr, key_str);
        cJSON_AddNumberToObject(si_json, settings_system_key[j], value);
        (*env)->DeleteLocalRef(env, key_str);
        (*env)->DeleteLocalRef(env, cr);
    }
    cJSON_AddItemToObject(json, "settings", si_json);
    logd(WD_COLLECT, "%s", "collect settings_info finished...");
}

void collect_display_info(JNIEnv *env, cJSON *json) {
    cJSON* di_json = cJSON_CreateObject();
    jobject rc = wdCallObjectMethod(env, g_app_context, "getResources", "()Landroid/content/res/Resources;");
    jobject dm = wdCallObjectMethod(env, rc, "getDisplayMetrics", "()Landroid/util/DisplayMetrics;");
    jint height = wdGetIntField(env, dm, "heightPixels", "I");
    jint width = wdGetIntField(env, dm, "widthPixels", "I");
    jint dpi = wdGetIntField(env, dm, "densityDpi", "I");
    cJSON_AddNumberToObject(di_json, "height", height);
    cJSON_AddNumberToObject(di_json, "width", width);
    cJSON_AddNumberToObject(di_json, "dpi", dpi);
    cJSON_AddItemToObject(json, "display", di_json);
    (*env)->DeleteLocalRef(env, rc);
    (*env)->DeleteLocalRef(env, dm);
    logd(WD_COLLECT, "%s", "collect display_info finished...");
}

void collect_proxy_info(JNIEnv *env, cJSON *json) {
    if(g_funcs.wd_system_property_get == NULL) return;
    cJSON* proxy_json = cJSON_CreateObject();
    const char* prop_key[] = {"http.proxyHost", "http.proxyPort"};
    char buf[32];
    for(int i = 0; i < sizeof(prop_key)/sizeof(const char*); ++i) {
        memset((void*)buf, 0, 32);
        g_funcs.wd_system_property_get(prop_key[i], buf);
        cJSON_AddStringToObject(proxy_json, prop_key[i], buf);
    }
    cJSON_AddItemToObject(json, "proxy", proxy_json);
    logd(WD_COLLECT, "%s", "collect proxy_info finished...");
}

void collect_camera_info(JNIEnv *env, cJSON *json) {
    //todo
}

void collect_battery_info(JNIEnv *env, cJSON *json) {
    cJSON* item_json = cJSON_CreateObject();

    //get battery capacity
    jobject power_profile = wdNewObject(env, "com/android/internal/os/PowerProfile", "(Landroid/content/Context;)V", g_app_context);
    jstring ca_jstr = (*env)->NewStringUTF(env, "battery.capacity");
    jdouble capacity = wdCallDoubleMethod(env, power_profile, "getAveragePower", "(Ljava/lang/String;)D", ca_jstr);
    cJSON_AddNumberToObject(item_json, "capacity", capacity);
    (*env)->DeleteLocalRef(env, power_profile);
    (*env)->DeleteLocalRef(env, ca_jstr);

    jobject action_jstr = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
    jobject intent_filter = wdNewObject(env, "android/content/IntentFilter", "(Ljava/lang/String;)V", action_jstr);
    jobject intent = wdCallObjectMethod(env, g_app_context, "registerReceiver",
            "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;", NULL, intent_filter);
    const char* key[] = {"status", "plugged", "level", "scale", "voltage", "temperature"};
    for(int i = 0; i < sizeof(key)/sizeof(const char*); ++i) {
        jstring key_str = (*env)->NewStringUTF(env, key[i]);
        jint value = wdCallIntMethod(env, intent, "getIntExtra", "(Ljava/lang/String;I)I", key_str, -1);
        cJSON_AddNumberToObject(item_json, key[i], value);
        (*env)->DeleteLocalRef(env, key_str);
    }

    cJSON_AddItemToObject(json, "battery", item_json);
    (*env)->DeleteLocalRef(env, action_jstr);
    (*env)->DeleteLocalRef(env, intent_filter);
    (*env)->DeleteLocalRef(env, intent);

    logd(WD_COLLECT, "%s", "collect battery_info finished...");
}

void collect_env(JNIEnv *env, cJSON *json) {
    //todo
}

void collect_libs_info(JNIEnv *env, cJSON *json) {
    cJSON* item = cJSON_CreateObject();
    const char* lib_names[] = {"libwdun.so", "libc.so"};
    int pid = WDSYSCALL(SYS_getpid);
    for(int j = 0; j < sizeof(lib_names)/sizeof(const char*); ++j) {
        char cmd[64] = {0};
        char line[1024] = {0};
        snprintf(cmd, 63, "cat /proc/%d/maps | grep %s", pid, lib_names[j]);
        FILE *fp = g_funcs.wd_popen(cmd, "r");
        if(fgets(line, 1024, fp)) {
            int pos = 0;
            sscanf(line, "%*lx-%*lx %*4s %*lx %*lx:%*lx %*d%n", &pos);
            char* path = wd_util_trim(line + pos);
            int fd = WDSYSCALL(SYS_openat, AT_FDCWD, path, O_RDONLY, 0);
            if(fd < 0) break;
            int size = lseek(fd, 0, SEEK_END);
            //映射内存
            int mmap_call_num = 222;
            uint8_t* base = (uint8_t*) WDSYSCALL(WD_SYS_mmap, 0, size, PROT_READ, MAP_SHARED, fd, 0);
            uint8_t out[16] = {0};
            char md5_str[33] = {0};
            MD5(base, size, out);
            bytes2Hex(out, md5_str, 16, 0);
            cJSON_AddStringToObject(item, lib_names[j], md5_str);
            //取消内存映射
            WDSYSCALL(SYS_munmap, base, size);
            //关闭文件
            WDSYSCALL(SYS_close, fd);
        }
        g_funcs.wd_pclose(fp);
    }
    cJSON_AddItemToObject(json, "libs", item);
    logd(WD_COLLECT, "%s", "collect libs_md5 finished...");
}

void collect_cpu_info(JNIEnv *env, cJSON *json) {
    //todo
}

void collect_sensor_info(JNIEnv *env, cJSON *json) {
    cJSON *item = cJSON_CreateArray();
    jstring sm_str = (*env)->NewStringUTF(env, "sensor");
    jobject sm = wdCallObjectMethod(env, g_app_context, "getSystemService",
                                    "(Ljava/lang/String;)Ljava/lang/Object;", sm_str);
    jobject sensor_list = wdCallObjectMethod(env, sm, "getSensorList", "(I)Ljava/util/List;", -1);
    jint size = wdCallIntMethod(env, sensor_list, "size", "()I");
    for (int i = 0; i < size; ++i) {
        jobject sensor = wdCallObjectMethod(env, sensor_list, "get", "(I)Ljava/lang/Object;", i);
        jint type = wdCallIntMethod(env, sensor, "getType", "()I");
        jobject name_jstr = wdCallObjectMethod(env, sensor, "getName", "()Ljava/lang/String;");
        jobject vendor_jstr = wdCallObjectMethod(env, sensor, "getVendor", "()Ljava/lang/String;");
        const char *name = (*env)->GetStringUTFChars(env, name_jstr, NULL);
        const char *vendor = (*env)->GetStringUTFChars(env, vendor_jstr, NULL);
        char buf[256] = {0};
        snprintf(buf, 255, "%d, %s, %s", type, name, vendor);
        cJSON_AddItemToArray(item, cJSON_CreateString(buf));
        (*env)->ReleaseStringUTFChars(env, name_jstr, name);
        (*env)->ReleaseStringUTFChars(env, vendor_jstr, vendor);
        (*env)->DeleteLocalRef(env, sensor);
    }
    (*env)->DeleteLocalRef(env, sensor_list);
    (*env)->DeleteLocalRef(env, sm);
    (*env)->DeleteLocalRef(env, sm_str);
    cJSON_AddItemToObject(json, "sensor", item);
    logd(WD_COLLECT, "%s", "collect sensor_info finished...");
}

void collect_mem_info(JNIEnv *env, cJSON *json) {
    cJSON* item = cJSON_CreateObject();

    //get ram
    struct sysinfo si;
    if(WDSYSCALL(SYS_sysinfo, &si) == 0) {
        double ram_total = ((double)(si.totalram * si.mem_unit)) / 1024 / 1024 / 1024;
        double ram_free = ((double)(si.freeram * si.mem_unit)) / 1024 / 1024 / 1024;
        char ram_buf[32] =  {0};
        snprintf(ram_buf, 31, "%.3lf, %.3lf", ram_total, ram_free);
        cJSON_AddStringToObject(item, "ram", ram_buf);
    }

    //get rom
    struct statfs sf;
    if(WDSYSCALL(SYS_statfs, "/data", &sf) == 0) {
        double rom_total = ((double)(sf.f_blocks * sf.f_bsize)) / 1024 / 1024 / 1024;
        double rom_free = ((double)(sf.f_bfree * sf.f_bsize)) / 1024 / 1024 / 1024;
        char rom_buf[32] =  {0};
        snprintf(rom_buf, 31, "%.3lf, %.3lf", rom_total, rom_free);
        cJSON_AddStringToObject(item, "rom", rom_buf);
    }

    //get sdcard
    struct statfs sf2;
    if(WDSYSCALL(SYS_statfs, "/sdcard", &sf2) == 0) {
        double sdcard_total = ((double)(sf2.f_blocks * sf2.f_bsize)) / 1024 / 1024 / 1024;
        double sdcard_free = ((double)(sf2.f_bfree * sf2.f_bsize)) / 1024 / 1024 / 1024;
        char sdcard_buf[32] =  {0};
        snprintf(sdcard_buf, 31, "%.3lf, %.3lf", sdcard_total, sdcard_free);
        cJSON_AddStringToObject(item, "sdcard", sdcard_buf);
    }

    cJSON_AddItemToObject(json, "mem", item);
    logd(WD_COLLECT, "%s", "collect mem_info finished...");
}

void collect_network_info(JNIEnv *env, cJSON *json) {
    jstring ci_jstr = (*env)->NewStringUTF(env, "connectivity");
    jobject ci = wdCallObjectMethod(env, g_app_context, "getSystemService",
                                    "(Ljava/lang/String;)Ljava/lang/Object;", ci_jstr);
    jobject ni = wdCallObjectMethod(env, ci, "getActiveNetworkInfo", "()Landroid/net/NetworkInfo;");
    if (ni == NULL) goto return_label;
    jint conn_type = wdCallIntMethod(env, ni, "getType", "()I");

    //mobile
    if (conn_type == 0) {
        char *network_type = "";
        jint sub_type = wdCallIntMethod(env, ni, "getSubtype", "()I");
        switch (sub_type) {
            case 1 :
            case 2 :
            case 4 :
            case 7 :
            case 11 :
            case 16 :
                network_type = "2g";
                break;
            case 3 :
            case 5 :
            case 6 :
            case 8 :
            case 9 :
            case 10 :
            case 12 :
            case 14 :
            case 15 :
            case 17 :
                network_type = "3g";
                break;
            case 13 :
            case 18 :
            case 19 :
                network_type = "4g";
                break;
            case 20 :
                network_type = "5g";
                break;
            default:
                break;
        }
        cJSON_AddStringToObject(json, "network_type", network_type);
    }

    //wifi
    else if (conn_type == 1) {
        cJSON_AddStringToObject(json, "network_type", "wifi");
        //get wifi ssid bssid ip, need ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION permission on Android high version
        jstring wm_jstr = (*env)->NewStringUTF(env, "wifi");
        jobject wm = wdCallObjectMethod(env, g_app_context, "getSystemService",
                                        "(Ljava/lang/String;)Ljava/lang/Object;", wm_jstr);
        jobject wi = wdCallObjectMethod(env, wm, "getConnectionInfo", "()Landroid/net/wifi/WifiInfo;");
        jobject ssid = wdCallObjectMethod(env, wi, "getSSID", "()Ljava/lang/String;");
        addJniStringToJson(env, json, "ssid", ssid);
        (*env)->DeleteLocalRef(env, ssid);
        jobject bssid = wdCallObjectMethod(env, wi, "getBSSID", "()Ljava/lang/String;");
        addJniStringToJson(env, json, "bssid", bssid);
        (*env)->DeleteLocalRef(env, bssid);
        jint wifi_ip_int = wdCallIntMethod(env, wi, "getIpAddress", "()I");
        char wifi_ip[32] = {0};
        inet_ntop(AF_INET, &wifi_ip_int, wifi_ip, 31);
        cJSON_AddStringToObject(json, "wifi_ip", wifi_ip);
        (*env)->DeleteLocalRef(env, wi);
        (*env)->DeleteLocalRef(env, wm);
        (*env)->DeleteLocalRef(env, wm_jstr);
    }

    (*env)->DeleteLocalRef(env, ni);
    return_label:
    (*env)->DeleteLocalRef(env, ci);
    (*env)->DeleteLocalRef(env, ci_jstr);

    logd(WD_COLLECT, "%s", "collect network info finished...");
}

void collect_user_agent(JNIEnv *env, cJSON *json) {
    jobject ua_jstr = wdCallStaticObjectMethod(env, "android/webkit/WebSettings", "getDefaultUserAgent",
            "(Landroid/content/Context;)Ljava/lang/String;", g_app_context);
    addJniStringToJson(env, json, "user-agent", ua_jstr);
    (*env)->DeleteLocalRef(env, ua_jstr);

    logd(WD_COLLECT, "%s", "collect user agent finished...");
}

void collect_location_info(JNIEnv *env, cJSON *json) {
    //get location info need ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION permission.
    cJSON* item = cJSON_CreateObject();

    jstring loc_jstr = (*env)->NewStringUTF(env, "location");
    jobject lm = wdCallObjectMethod(env, g_app_context, "getSystemService",
                                    "(Ljava/lang/String;)Ljava/lang/Object;", loc_jstr);
    jobject providers = wdCallObjectMethod(env, lm, "getAllProviders", "()Ljava/util/List;");
    if(providers == NULL) goto return_label;
    jint size = wdCallIntMethod(env, providers, "size", "()I");
    int get_loc = 0;
    for(int i = 0; i < size; ++i) {
        jobject provider_jstr = wdCallObjectMethod(env, providers, "get", "(I)Ljava/lang/Object;", i);
        //check if location provider is available
        if(wdCallBooleanMethod(env, lm, "isProviderEnabled", "(Ljava/lang/String;)Z", provider_jstr) == JNI_TRUE) {
            jobject loc = wdCallObjectMethod(env, lm, "getLastKnownLocation",
                                             "(Ljava/lang/String;)Landroid/location/Location;", provider_jstr);
            if(loc != NULL) {
                jdouble lat = wdCallDoubleMethod(env, loc, "getLatitude", "()D");
                jdouble lon = wdCallDoubleMethod(env, loc, "getLongitude", "()D");
                cJSON_AddNumberToObject(item, "lat", lat);
                cJSON_AddNumberToObject(item, "lon", lon);
                get_loc = 1;
            }
            (*env)->DeleteLocalRef(env, loc);
        }
        (*env)->DeleteLocalRef(env, provider_jstr);
        if(get_loc) break;
    }
    (*env)->DeleteLocalRef(env, providers);
    return_label:
    (*env)->DeleteLocalRef(env, lm);
    (*env)->DeleteLocalRef(env, loc_jstr);
    cJSON_AddItemToObject(json, "location", item);
    logd(WD_COLLECT, "%s", "collect location info finished...");
}

void collect_cell_info(JNIEnv *env, cJSON *json) {
    //cell info need ACCESSS_COARSE_LOCATION permission
    cJSON* item = cJSON_CreateObject();

    jstring phone_jstr = (*env)->NewStringUTF(env, "phone");
    jobject tm = wdCallObjectMethod(env, g_app_context, "getSystemService",
                                    "(Ljava/lang/String;)Ljava/lang/Object;", phone_jstr);
    jobject cl = wdCallObjectMethod(env, tm, "getCellLocation", "()Landroid/telephony/CellLocation;");
    if(cl == NULL) goto return_label;
    jclass gsm_cls = (*env)->FindClass(env, "android/telephony/gsm/GsmCellLocation");
    if((*env)->IsInstanceOf(env, cl, gsm_cls) == JNI_TRUE) {
        jint cid = wdCallIntMethod(env, cl, "getCid", "()I");
        jint lac = wdCallIntMethod(env, cl, "getLac", "()I");
        jint psc = wdCallIntMethod(env, cl, "getPsc", "()I");
        cJSON_AddNumberToObject(item, "cid", cid);
        cJSON_AddNumberToObject(item, "lac", lac);
        cJSON_AddNumberToObject(item, "psc", psc);
    }
    (*env)->DeleteLocalRef(env, gsm_cls);
    return_label:
    (*env)->DeleteLocalRef(env, cl);
    (*env)->DeleteLocalRef(env, tm);
    (*env)->DeleteLocalRef(env, phone_jstr);

    cJSON_AddItemToObject(json, "cell", item);
    logd(WD_COLLECT, "%s", "collect cell info finished...");
}