mirror of https://github.com/PurpleI2P/i2pd.git
unlnown542a
7 years ago
6 changed files with 227 additions and 351 deletions
@ -1,193 +1,193 @@ |
|||||||
#include "DaemonAndroid.h" |
#include "DaemonAndroid.h" |
||||||
#include "Daemon.h" |
|
||||||
#include <iostream> |
|
||||||
#include <boost/exception/diagnostic_information.hpp> |
|
||||||
#include <boost/exception_ptr.hpp> |
|
||||||
#include <exception> |
|
||||||
//#include "mainwindow.h"
|
|
||||||
|
|
||||||
namespace i2p |
#ifndef _WIN32 |
||||||
{ |
|
||||||
namespace android |
|
||||||
{ |
|
||||||
/* Worker::Worker (DaemonAndroidImpl& daemon):
|
|
||||||
m_Daemon (daemon) |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
void Worker::startDaemon() |
#include <signal.h> |
||||||
{ |
#include <stdlib.h> |
||||||
Log.d(TAG"Performing daemon start..."); |
#include <thread> |
||||||
m_Daemon.start(); |
#include <unistd.h> |
||||||
Log.d(TAG"Daemon started."); |
#include <fcntl.h> |
||||||
emit resultReady(); |
#include <sys/stat.h> |
||||||
} |
#include <sys/resource.h> |
||||||
void Worker::restartDaemon() |
|
||||||
{ |
|
||||||
Log.d(TAG"Performing daemon restart..."); |
|
||||||
m_Daemon.restart(); |
|
||||||
Log.d(TAG"Daemon restarted."); |
|
||||||
emit resultReady(); |
|
||||||
} |
|
||||||
void Worker::stopDaemon() { |
|
||||||
Log.d(TAG"Performing daemon stop..."); |
|
||||||
m_Daemon.stop(); |
|
||||||
Log.d(TAG"Daemon stopped."); |
|
||||||
emit resultReady(); |
|
||||||
} |
|
||||||
|
|
||||||
Controller::Controller(DaemonAndroidImpl& daemon): |
#include "Config.h" |
||||||
m_Daemon (daemon) |
#include "FS.h" |
||||||
{ |
#include "Log.h" |
||||||
Worker *worker = new Worker (m_Daemon); |
#include "Tunnel.h" |
||||||
worker->moveToThread(&workerThread); |
#include "RouterContext.h" |
||||||
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); |
#include "ClientContext.h" |
||||||
connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); |
|
||||||
connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); |
|
||||||
connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); |
|
||||||
connect(worker, &Worker::resultReady, this, &Controller::handleResults); |
|
||||||
workerThread.start(); |
|
||||||
} |
|
||||||
Controller::~Controller() |
|
||||||
{ |
|
||||||
Log.d(TAG"Closing and waiting for daemon worker thread..."); |
|
||||||
workerThread.quit(); |
|
||||||
workerThread.wait(); |
|
||||||
Log.d(TAG"Waiting for daemon worker thread finished."); |
|
||||||
if(m_Daemon.isRunning()) |
|
||||||
{ |
|
||||||
Log.d(TAG"Stopping the daemon..."); |
|
||||||
m_Daemon.stop(); |
|
||||||
Log.d(TAG"Stopped the daemon."); |
|
||||||
} |
|
||||||
} |
|
||||||
*/ |
|
||||||
DaemonAndroidImpl::DaemonAndroidImpl () |
|
||||||
//:
|
|
||||||
/*mutex(nullptr), */ |
|
||||||
//m_IsRunning(false),
|
|
||||||
//m_RunningChangedCallback(nullptr)
|
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
DaemonAndroidImpl::~DaemonAndroidImpl () |
void handle_signal(int sig) |
||||||
{ |
{ |
||||||
//delete mutex;
|
switch (sig) |
||||||
} |
{ |
||||||
|
case SIGHUP: |
||||||
bool DaemonAndroidImpl::init(int argc, char* argv[]) |
LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening tunnel configuration..."); |
||||||
{ |
i2p::client::context.ReloadConfig(); |
||||||
//mutex=new QMutex(QMutex::Recursive);
|
break; |
||||||
//setRunningCallback(0);
|
case SIGUSR1: |
||||||
//m_IsRunning=false;
|
LogPrint(eLogInfo, "Daemon: Got SIGUSR1, reopening logs..."); |
||||||
return Daemon.init(argc,argv); |
i2p::log::Logger().Reopen (); |
||||||
} |
break; |
||||||
|
case SIGINT: |
||||||
void DaemonAndroidImpl::start() |
if (i2p::context.AcceptsTunnels () && !Daemon.gracefulShutdownInterval) |
||||||
{ |
{ |
||||||
//QMutexLocker locker(mutex);
|
i2p::context.SetAcceptsTunnels (false); |
||||||
//setRunning(true);
|
Daemon.gracefulShutdownInterval = 10*60; // 10 minutes
|
||||||
Daemon.start(); |
LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefulShutdownInterval, " seconds"); |
||||||
|
} |
||||||
|
else |
||||||
|
Daemon.running = 0; |
||||||
|
break; |
||||||
|
case SIGABRT: |
||||||
|
case SIGTERM: |
||||||
|
Daemon.running = 0; // Exit loop
|
||||||
|
break; |
||||||
|
case SIGPIPE: |
||||||
|
LogPrint(eLogInfo, "SIGPIPE received"); |
||||||
|
break; |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
void DaemonAndroidImpl::stop() |
namespace i2p |
||||||
|
{ |
||||||
|
namespace util |
||||||
{ |
{ |
||||||
//QMutexLocker locker(mutex);
|
bool DaemonAndroid::start() |
||||||
Daemon.stop(); |
{ |
||||||
//setRunning(false);
|
if (isDaemon) |
||||||
} |
{ |
||||||
|
pid_t pid; |
||||||
|
pid = fork(); |
||||||
|
if (pid > 0) // parent
|
||||||
|
::exit (EXIT_SUCCESS); |
||||||
|
|
||||||
void DaemonAndroidImpl::restart() |
if (pid < 0) // error
|
||||||
{ |
{ |
||||||
//QMutexLocker locker(mutex);
|
LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno)); |
||||||
stop(); |
return false; |
||||||
start(); |
} |
||||||
} |
|
||||||
/*
|
|
||||||
void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb) |
|
||||||
{ |
|
||||||
m_RunningChangedCallback = cb; |
|
||||||
} |
|
||||||
|
|
||||||
bool DaemonAndroidImpl::isRunning() |
// child
|
||||||
{ |
umask(S_IWGRP | S_IRWXO); // 0027
|
||||||
return m_IsRunning; |
int sid = setsid(); |
||||||
} |
if (sid < 0) |
||||||
|
{ |
||||||
|
LogPrint(eLogError, "Daemon: could not create process group."); |
||||||
|
return false; |
||||||
|
} |
||||||
|
std::string d = i2p::fs::GetDataDir(); |
||||||
|
if (chdir(d.c_str()) != 0) |
||||||
|
{ |
||||||
|
LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno)); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
void DaemonAndroidImpl::setRunning(bool newValue) |
// point std{in,out,err} descriptors to /dev/null
|
||||||
{ |
freopen("/dev/null", "r", stdin); |
||||||
bool oldValue = m_IsRunning; |
freopen("/dev/null", "w", stdout); |
||||||
if(oldValue!=newValue) |
freopen("/dev/null", "w", stderr); |
||||||
{ |
} |
||||||
m_IsRunning = newValue; |
|
||||||
if(m_RunningChangedCallback) |
|
||||||
m_RunningChangedCallback(); |
|
||||||
} |
|
||||||
} |
|
||||||
*/ |
|
||||||
static DaemonAndroidImpl daemon; |
|
||||||
static char* argv[1]={strdup("tmp")}; |
|
||||||
/**
|
|
||||||
* returns error details if failed |
|
||||||
* returns "ok" if daemon initialized and started okay |
|
||||||
*/ |
|
||||||
std::string start(/*int argc, char* argv[]*/) |
|
||||||
{ |
|
||||||
try |
|
||||||
{ |
|
||||||
//int result;
|
|
||||||
|
|
||||||
|
// set proc limits
|
||||||
|
struct rlimit limit; |
||||||
|
uint16_t nfiles; i2p::config::GetOption("limits.openfiles", nfiles); |
||||||
|
getrlimit(RLIMIT_NOFILE, &limit); |
||||||
|
if (nfiles == 0) { |
||||||
|
LogPrint(eLogInfo, "Daemon: using system limit in ", limit.rlim_cur, " max open files"); |
||||||
|
} else if (nfiles <= limit.rlim_max) { |
||||||
|
limit.rlim_cur = nfiles; |
||||||
|
if (setrlimit(RLIMIT_NOFILE, &limit) == 0) { |
||||||
|
LogPrint(eLogInfo, "Daemon: set max number of open files to ", |
||||||
|
nfiles, " (system limit is ", limit.rlim_max, ")"); |
||||||
|
} else { |
||||||
|
LogPrint(eLogError, "Daemon: can't set max number of open files: ", strerror(errno)); |
||||||
|
} |
||||||
|
} else { |
||||||
|
LogPrint(eLogError, "Daemon: limits.openfiles exceeds system limit: ", limit.rlim_max); |
||||||
|
} |
||||||
|
uint32_t cfsize; i2p::config::GetOption("limits.coresize", cfsize); |
||||||
|
if (cfsize) // core file size set
|
||||||
{ |
{ |
||||||
//Log.d(TAG"Initialising the daemon...");
|
cfsize *= 1024; |
||||||
bool daemonInitSuccess = daemon.init(1,argv); |
getrlimit(RLIMIT_CORE, &limit); |
||||||
if(!daemonInitSuccess) |
if (cfsize <= limit.rlim_max) { |
||||||
{ |
limit.rlim_cur = cfsize; |
||||||
//QMessageBox::critical(0, "Error", "Daemon init failed");
|
if (setrlimit(RLIMIT_CORE, &limit) != 0) { |
||||||
return "Daemon init failed"; |
LogPrint(eLogError, "Daemon: can't set max size of coredump: ", strerror(errno)); |
||||||
|
} else if (cfsize == 0) { |
||||||
|
LogPrint(eLogInfo, "Daemon: coredumps disabled"); |
||||||
|
} else { |
||||||
|
LogPrint(eLogInfo, "Daemon: set max size of core files to ", cfsize / 1024, "Kb"); |
||||||
|
} |
||||||
|
} else { |
||||||
|
LogPrint(eLogError, "Daemon: limits.coresize exceeds system limit: ", limit.rlim_max); |
||||||
} |
} |
||||||
//Log.d(TAG"Initialised, creating the main window...");
|
} |
||||||
//MainWindow w;
|
|
||||||
//Log.d(TAG"Before main window.show()...");
|
|
||||||
//w.show ();
|
|
||||||
|
|
||||||
|
// Pidfile
|
||||||
|
// this code is c-styled and a bit ugly
|
||||||
|
std::string pidfile; i2p::config::GetOption("pidfile", pidfile); |
||||||
|
if (pidfile == "") { |
||||||
|
pidfile = i2p::fs::DataDirPath("i2pd.pid"); |
||||||
|
} |
||||||
|
if (pidfile != "") { |
||||||
|
pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); |
||||||
|
if (pidFH < 0) |
||||||
|
{ |
||||||
|
LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); |
||||||
|
return false; |
||||||
|
} |
||||||
|
char pid[10]; |
||||||
|
sprintf(pid, "%d\n", getpid()); |
||||||
|
ftruncate(pidFH, 0); |
||||||
|
if (write(pidFH, pid, strlen(pid)) < 0) |
||||||
{ |
{ |
||||||
//i2p::qt::Controller daemonQtController(daemon);
|
LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno)); |
||||||
//Log.d(TAG"Starting the daemon...");
|
return false; |
||||||
//emit daemonQtController.startDaemon();
|
|
||||||
//daemon.start ();
|
|
||||||
//Log.d(TAG"Starting GUI event loop...");
|
|
||||||
//result = app.exec();
|
|
||||||
//daemon.stop ();
|
|
||||||
daemon.start(); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
gracefulShutdownInterval = 0; // not specified
|
||||||
|
|
||||||
//QMessageBox::information(&w, "Debug", "demon stopped");
|
// Signal handler
|
||||||
//Log.d(TAG"Exiting the application");
|
struct sigaction sa; |
||||||
//return result;
|
sa.sa_handler = handle_signal; |
||||||
} |
sigemptyset(&sa.sa_mask); |
||||||
catch (boost::exception& ex) |
sa.sa_flags = SA_RESTART; |
||||||
{ |
sigaction(SIGHUP, &sa, 0); |
||||||
std::stringstream ss; |
sigaction(SIGUSR1, &sa, 0); |
||||||
ss << boost::diagnostic_information(ex); |
sigaction(SIGABRT, &sa, 0); |
||||||
return ss.str(); |
sigaction(SIGTERM, &sa, 0); |
||||||
|
sigaction(SIGINT, &sa, 0); |
||||||
|
sigaction(SIGPIPE, &sa, 0); |
||||||
|
|
||||||
|
return Daemon_Singleton::start(); |
||||||
} |
} |
||||||
catch (std::exception& ex) |
|
||||||
|
bool DaemonAndroid::stop() |
||||||
{ |
{ |
||||||
std::stringstream ss; |
i2p::fs::Remove(pidfile); |
||||||
ss << ex.what(); |
|
||||||
return ss.str(); |
return Daemon_Singleton::stop(); |
||||||
} |
} |
||||||
catch(...) |
|
||||||
|
void DaemonAndroid::run () |
||||||
{ |
{ |
||||||
return "unknown exception"; |
while (running) |
||||||
|
{ |
||||||
|
std::this_thread::sleep_for (std::chrono::seconds(1)); |
||||||
|
if (gracefulShutdownInterval) |
||||||
|
{ |
||||||
|
gracefulShutdownInterval--; // - 1 second
|
||||||
|
if (gracefulShutdownInterval <= 0 || i2p::tunnel::tunnels.CountTransitTunnels() <= 0) |
||||||
|
{ |
||||||
|
LogPrint(eLogInfo, "Graceful shutdown"); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
return "ok"; |
|
||||||
} |
|
||||||
|
|
||||||
void stop() |
|
||||||
{ |
|
||||||
daemon.stop(); |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
|
||||||
|
#endif |
||||||
|
@ -1,87 +1,60 @@ |
|||||||
#ifndef DAEMON_ANDROID_H |
#ifndef DAEMON_H__ |
||||||
#define DAEMON_ANDROID_H |
#define DAEMON_H__ |
||||||
|
|
||||||
|
#include <memory> |
||||||
#include <string> |
#include <string> |
||||||
|
|
||||||
namespace i2p |
namespace i2p |
||||||
{ |
{ |
||||||
namespace android |
namespace util |
||||||
{ |
{ |
||||||
class DaemonAndroidImpl |
class Daemon_Singleton_Private; |
||||||
{ |
class Daemon_Singleton |
||||||
public: |
{ |
||||||
|
public: |
||||||
DaemonAndroidImpl (); |
virtual bool init(int argc, char* argv[]); |
||||||
~DaemonAndroidImpl (); |
virtual bool start(); |
||||||
|
virtual bool stop(); |
||||||
//typedef void (*runningChangedCallback)();
|
virtual void run () {}; |
||||||
|
|
||||||
/**
|
bool isDaemon; |
||||||
* @return success |
bool running; |
||||||
*/ |
|
||||||
bool init(int argc, char* argv[]); |
protected: |
||||||
void start(); |
Daemon_Singleton(); |
||||||
void stop(); |
virtual ~Daemon_Singleton(); |
||||||
void restart(); |
|
||||||
//void setRunningCallback(runningChangedCallback cb);
|
bool IsService () const; |
||||||
//bool isRunning();
|
|
||||||
private: |
// d-pointer for httpServer, httpProxy, etc.
|
||||||
//void setRunning(bool running);
|
class Daemon_Singleton_Private; |
||||||
private: |
Daemon_Singleton_Private &d; |
||||||
//QMutex* mutex;
|
}; |
||||||
//bool m_IsRunning;
|
|
||||||
//runningChangedCallback m_RunningChangedCallback;
|
#if defined(ANDROID) |
||||||
}; |
#define Daemon i2p::util::DaemonAndroid::Instance() |
||||||
|
class DaemonAndroid : public Daemon_Singleton |
||||||
/**
|
{ |
||||||
* returns "ok" if daemon init failed |
public: |
||||||
* returns errinfo if daemon initialized and started okay |
static DaemonAndroid& Instance() |
||||||
*/ |
{ |
||||||
std::string start(); |
static DaemonAndroid instance; |
||||||
|
return instance; |
||||||
// stops the daemon
|
} |
||||||
void stop(); |
|
||||||
|
bool start(); |
||||||
/*
|
bool stop(); |
||||||
class Worker : public QObject |
void run (); |
||||||
{ |
|
||||||
Q_OBJECT |
private: |
||||||
public: |
std::string pidfile; |
||||||
|
int pidFH; |
||||||
Worker (DaemonAndroidImpl& daemon); |
|
||||||
|
public: |
||||||
private: |
int gracefulShutdownInterval; // in seconds
|
||||||
|
}; |
||||||
DaemonAndroidImpl& m_Daemon; |
#endif |
||||||
|
|
||||||
public slots: |
|
||||||
void startDaemon(); |
|
||||||
void restartDaemon(); |
|
||||||
void stopDaemon(); |
|
||||||
|
|
||||||
signals: |
|
||||||
void resultReady(); |
|
||||||
}; |
|
||||||
|
|
||||||
class Controller : public QObject |
|
||||||
{ |
|
||||||
Q_OBJECT |
|
||||||
QThread workerThread; |
|
||||||
public: |
|
||||||
Controller(DaemonAndroidImpl& daemon); |
|
||||||
~Controller(); |
|
||||||
private: |
|
||||||
DaemonAndroidImpl& m_Daemon; |
|
||||||
|
|
||||||
public slots: |
|
||||||
void handleResults(){} |
|
||||||
signals: |
|
||||||
void startDaemon(); |
|
||||||
void stopDaemon(); |
|
||||||
void restartDaemon(); |
|
||||||
}; |
|
||||||
*/ |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
#endif // DAEMON_ANDROID_H
|
#endif // DAEMON_H__
|
||||||
|
@ -1,66 +0,0 @@ |
|||||||
|
|
||||||
//#include <string.h>
|
|
||||||
#include <jni.h> |
|
||||||
#include "org_purplei2p_i2pd_I2PD_JNI.h" |
|
||||||
#include "DaemonAndroid.h" |
|
||||||
#include "RouterContext.h" |
|
||||||
#include "Transports.h" |
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith |
|
||||||
(JNIEnv * env, jclass clazz) { |
|
||||||
#if defined(__arm__) |
|
||||||
#if defined(__ARM_ARCH_7A__) |
|
||||||
#if defined(__ARM_NEON__) |
|
||||||
#if defined(__ARM_PCS_VFP) |
|
||||||
#define ABI "armeabi-v7a/NEON (hard-float)" |
|
||||||
#else |
|
||||||
#define ABI "armeabi-v7a/NEON" |
|
||||||
#endif |
|
||||||
#else |
|
||||||
#if defined(__ARM_PCS_VFP) |
|
||||||
#define ABI "armeabi-v7a (hard-float)" |
|
||||||
#else |
|
||||||
#define ABI "armeabi-v7a" |
|
||||||
#endif |
|
||||||
#endif |
|
||||||
#else |
|
||||||
#define ABI "armeabi" |
|
||||||
#endif |
|
||||||
#elif defined(__i386__) |
|
||||||
#define ABI "x86" |
|
||||||
#elif defined(__x86_64__) |
|
||||||
#define ABI "x86_64" |
|
||||||
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ |
|
||||||
#define ABI "mips64" |
|
||||||
#elif defined(__mips__) |
|
||||||
#define ABI "mips" |
|
||||||
#elif defined(__aarch64__) |
|
||||||
#define ABI "arm64-v8a" |
|
||||||
#else |
|
||||||
#define ABI "unknown" |
|
||||||
#endif |
|
||||||
|
|
||||||
return env->NewStringUTF(ABI); |
|
||||||
} |
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon |
|
||||||
(JNIEnv * env, jclass clazz) { |
|
||||||
return env->NewStringUTF(i2p::android::start().c_str()); |
|
||||||
} |
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon |
|
||||||
(JNIEnv * env, jclass clazz) { |
|
||||||
i2p::android::stop(); |
|
||||||
} |
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels |
|
||||||
(JNIEnv * env, jclass clazz) { |
|
||||||
i2p::context.SetAcceptsTunnels (false); |
|
||||||
} |
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged |
|
||||||
(JNIEnv * env, jclass clazz, jboolean isConnected) |
|
||||||
{ |
|
||||||
bool isConnectedBool = (bool) isConnected; |
|
||||||
i2p::transport::transports.SetOnline (isConnectedBool); |
|
||||||
} |
|
@ -1,33 +0,0 @@ |
|||||||
/* DO NOT EDIT THIS FILE - it is machine generated */ |
|
||||||
#include <jni.h> |
|
||||||
/* Header for class org_purplei2p_i2pd_I2PD_JNI */ |
|
||||||
|
|
||||||
#ifndef _Included_org_purplei2p_i2pd_I2PD_JNI |
|
||||||
#define _Included_org_purplei2p_i2pd_I2PD_JNI |
|
||||||
#ifdef __cplusplus |
|
||||||
extern "C" { |
|
||||||
#endif |
|
||||||
/*
|
|
||||||
* Class: org_purplei2p_i2pd_I2PD_JNI |
|
||||||
* Method: stringFromJNI |
|
||||||
* Signature: ()Ljava/lang/String; |
|
||||||
*/ |
|
||||||
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith |
|
||||||
(JNIEnv *, jclass); |
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon |
|
||||||
(JNIEnv *, jclass); |
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon |
|
||||||
(JNIEnv *, jclass); |
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels |
|
||||||
(JNIEnv *, jclass); |
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged |
|
||||||
(JNIEnv * env, jclass clazz, jboolean isConnected); |
|
||||||
|
|
||||||
#ifdef __cplusplus |
|
||||||
} |
|
||||||
#endif |
|
||||||
#endif |
|
Loading…
Reference in new issue