diff --git a/.gitignore b/.gitignore index 89a17a3c..b6cffd15 100644 --- a/.gitignore +++ b/.gitignore @@ -237,3 +237,4 @@ pip-log.txt # Sphinx docs/_build +/androidIdea/ diff --git a/Daemon.h b/Daemon.h index fa4f47ec..85b31240 100644 --- a/Daemon.h +++ b/Daemon.h @@ -45,6 +45,20 @@ namespace i2p } }; +#elif defined(ANDROID) +#define Daemon i2p::util::DaemonAndroid::Instance() + // dummy, invoked from android/jni/DaemonAndroid.* + class DaemonAndroid: public i2p::util::Daemon_Singleton + { + public: + + static DaemonAndroid& Instance() + { + static DaemonAndroid instance; + return instance; + } + }; + #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton diff --git a/HTTPServer.cpp b/HTTPServer.cpp index cfcf4261..39ced94a 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -377,7 +377,7 @@ namespace http { s << " Stop accepting tunnels
\r\n"; else s << " Start accepting tunnels
\r\n"; -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefullShutdownInterval) { s << " Cancel gracefull shutdown ("; s << Daemon.gracefullShutdownInterval; @@ -690,12 +690,12 @@ namespace http { i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefullShutdownInterval = 10*60; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefullShutdownInterval = 0; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..8364f857 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,4 @@ +/gen/ +/libs/ +/tests/ +.idea diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100755 index 00000000..b6cc6f26 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/jni/Android.mk b/android/jni/Android.mk new file mode 100755 index 00000000..90a679b2 --- /dev/null +++ b/android/jni/Android.mk @@ -0,0 +1,113 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := i2pd +LOCAL_CPP_FEATURES := rtti exceptions +LOCAL_C_INCLUDES += $(IFADDRS_PATH) ../.. +LOCAL_STATIC_LIBRARIES := \ + boost_system-gcc-mt-1_53 \ + boost_date_time-gcc-mt-1_53 \ + boost_filesystem-gcc-mt-1_53 \ + boost_program_options-gcc-mt-1_53 \ + crypto ssl \ + miniupnpc +LOCAL_LDLIBS := -lz + +LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \ + $(IFADDRS_PATH)/ifaddrs.c \ + ../../HTTPServer.cpp ../../I2PControl.cpp ../../Daemon.cpp ../../Config.cpp \ + ../../AddressBook.cpp \ + ../../api.cpp \ + ../../Base.cpp \ + ../../BOB.cpp \ + ../../ClientContext.cpp \ + ../../Crypto.cpp \ + ../../Datagram.cpp \ + ../../Destination.cpp \ + ../../Family.cpp \ + ../../FS.cpp \ + ../../Garlic.cpp \ + ../../Gzip.cpp \ + ../../HTTP.cpp \ + ../../HTTPProxy.cpp \ + ../../I2CP.cpp \ + ../../I2NPProtocol.cpp \ + ../../I2PEndian.cpp \ + ../../I2PService.cpp \ + ../../I2PTunnel.cpp \ + ../../Identity.cpp \ + ../../LeaseSet.cpp \ + ../../Log.cpp \ + ../../NetDb.cpp \ + ../../NetDbRequests.cpp \ + ../../NTCPSession.cpp \ + ../../Profiling.cpp \ + ../../Reseed.cpp \ + ../../RouterContext.cpp \ + ../../RouterInfo.cpp \ + ../../SAM.cpp \ + ../../Signature.cpp \ + ../../SOCKS.cpp \ + ../../SSU.cpp \ + ../../SSUData.cpp \ + ../../SSUSession.cpp \ + ../../Streaming.cpp \ + ../../TransitTunnel.cpp \ + ../../Transports.cpp \ + ../../Tunnel.cpp \ + ../../TunnelEndpoint.cpp \ + ../../TunnelGateway.cpp \ + ../../TunnelPool.cpp \ + ../../util.cpp \ + ../../i2pd.cpp ../../UPnP.cpp + +include $(BUILD_SHARED_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_system-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_system-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_date_time-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_filesystem-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_program_options-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := crypto +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.0.2/$(TARGET_ARCH_ABI)/lib/libcrypto.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.0.2/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := ssl +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.0.2/$(TARGET_ARCH_ABI)/lib/libssl.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.0.2/include +LOCAL_STATIC_LIBRARIES := crypto +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := miniupnpc +LOCAL_SRC_FILES := $(MINIUPNP_PATH)/miniupnp-2.0/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a +LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnp-2.0/include +include $(PREBUILT_STATIC_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk new file mode 100755 index 00000000..e8a51add --- /dev/null +++ b/android/jni/Application.mk @@ -0,0 +1,32 @@ +#APP_ABI := all +#APP_ABI := armeabi-v7a x86 +#APP_ABI := x86 +APP_ABI := armeabi-v7a +#can be android-3 but will fail for x86 since arch-x86 is not present at ndkroot/platforms/android-3/ . libz is taken from there. +APP_PLATFORM := android-9 + +# http://stackoverflow.com/a/21386866/529442 http://stackoverflow.com/a/15616255/529442 to enable c++11 support in Eclipse +NDK_TOOLCHAIN_VERSION := 4.9 +# APP_STL := stlport_shared --> does not seem to contain C++11 features +APP_STL := gnustl_shared + +# Enable c++11 extentions in source code +APP_CPPFLAGS += -std=c++11 + +APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +APP_CPPFLAGS += -DANDROID_ARM7A +endif + +APP_OPTIM := debug + +# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/android-ifaddrs.git +# change to your own +I2PD_LIBS_PATH=/path/to/libraries +BOOST_PATH = $(I2PD_LIBS_PATH)/Boost-for-Android-Prebuilt +OPENSSL_PATH = $(I2PD_LIBS_PATH)/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = $(I2PD_LIBS_PATH)/MiniUPnP-for-Android-Prebuilt +IFADDRS_PATH = $(I2PD_LIBS_PATH)/android-ifaddrs diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp new file mode 100644 index 00000000..038a07fa --- /dev/null +++ b/android/jni/DaemonAndroid.cpp @@ -0,0 +1,194 @@ +#include "DaemonAndroid.h" +#include "../../Daemon.h" +#include +#include +#include +#include +//#include "mainwindow.h" + +namespace i2p +{ +namespace android +{ +/* Worker::Worker (DaemonAndroidImpl& daemon): + m_Daemon (daemon) + { + } + + void Worker::startDaemon() + { + Log.d(TAG"Performing daemon start..."); + m_Daemon.start(); + Log.d(TAG"Daemon started."); + emit resultReady(); + } + 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): + m_Daemon (daemon) + { + Worker *worker = new Worker (m_Daemon); + worker->moveToThread(&workerThread); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + 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 () + { + //delete mutex; + } + + bool DaemonAndroidImpl::init(int argc, char* argv[]) + { + //mutex=new QMutex(QMutex::Recursive); + //setRunningCallback(0); + //m_IsRunning=false; + return Daemon.init(argc,argv); + } + + void DaemonAndroidImpl::start() + { + //QMutexLocker locker(mutex); + //setRunning(true); + Daemon.start(); + } + + void DaemonAndroidImpl::stop() + { + //QMutexLocker locker(mutex); + Daemon.stop(); + //setRunning(false); + } + + void DaemonAndroidImpl::restart() + { + //QMutexLocker locker(mutex); + stop(); + start(); + } + /* + void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb) + { + m_RunningChangedCallback = cb; + } + + bool DaemonAndroidImpl::isRunning() + { + return m_IsRunning; + } + + void DaemonAndroidImpl::setRunning(bool newValue) + { + bool oldValue = m_IsRunning; + if(oldValue!=newValue) + { + 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; + + { + //Log.d(TAG"Initialising the daemon..."); + bool daemonInitSuccess = daemon.init(1,argv); + if(!daemonInitSuccess) + { + //QMessageBox::critical(0, "Error", "Daemon init failed"); + return "Daemon init failed"; + } + //Log.d(TAG"Initialised, creating the main window..."); + //MainWindow w; + //Log.d(TAG"Before main window.show()..."); + //w.show (); + + { + //i2p::qt::Controller daemonQtController(daemon); + //Log.d(TAG"Starting the daemon..."); + //emit daemonQtController.startDaemon(); + //daemon.start (); + //Log.d(TAG"Starting GUI event loop..."); + //result = app.exec(); + //daemon.stop (); + daemon.start(); + } + } + + //QMessageBox::information(&w, "Debug", "demon stopped"); + //Log.d(TAG"Exiting the application"); + //return result; + } + catch (boost::exception& ex) + { + std::stringstream ss; + ss << boost::diagnostic_information(ex); + return ss.str(); + } + catch (std::exception& ex) + { + std::stringstream ss; + ss << ex.what(); + return ss.str(); + } + catch(...) + { + return "unknown exception"; + } + return "ok"; + } + + void stop() + { + daemon.stop(); + } +} +} + diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h new file mode 100644 index 00000000..9cc8219b --- /dev/null +++ b/android/jni/DaemonAndroid.h @@ -0,0 +1,87 @@ +#ifndef DAEMON_ANDROID_H +#define DAEMON_ANDROID_H + +#include + +namespace i2p +{ +namespace android +{ + class DaemonAndroidImpl + { + public: + + DaemonAndroidImpl (); + ~DaemonAndroidImpl (); + + //typedef void (*runningChangedCallback)(); + + /** + * @return success + */ + bool init(int argc, char* argv[]); + void start(); + void stop(); + void restart(); + //void setRunningCallback(runningChangedCallback cb); + //bool isRunning(); + private: + //void setRunning(bool running); + private: + //QMutex* mutex; + //bool m_IsRunning; + //runningChangedCallback m_RunningChangedCallback; + }; + + /** + * returns "ok" if daemon init failed + * returns errinfo if daemon initialized and started okay + */ + std::string start(); + + // stops the daemon + void stop(); + + /* + class Worker : public QObject + { + Q_OBJECT + public: + + Worker (DaemonAndroidImpl& daemon); + + private: + + DaemonAndroidImpl& m_Daemon; + + 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 diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp new file mode 100755 index 00000000..40b50345 --- /dev/null +++ b/android/jni/i2pd_android.cpp @@ -0,0 +1,63 @@ + +//#include +#include +#include "org_purplei2p_i2pd_I2PD_JNI.h" +#include "DaemonAndroid.h" +#include "../../RouterContext.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; +} diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h new file mode 100644 index 00000000..04923d22 --- /dev/null +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -0,0 +1,33 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* 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 diff --git a/android/project.properties b/android/project.properties new file mode 100644 index 00000000..7ce68660 --- /dev/null +++ b/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-24 diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png new file mode 100644 index 00000000..a5dc7b68 Binary files /dev/null and b/android/res/drawable/icon.png differ diff --git a/android/res/drawable/itoopie_notification_icon.png b/android/res/drawable/itoopie_notification_icon.png new file mode 100644 index 00000000..8fbe2468 Binary files /dev/null and b/android/res/drawable/itoopie_notification_icon.png differ diff --git a/android/res/menu/options_main.xml b/android/res/menu/options_main.xml new file mode 100644 index 00000000..388dfd83 --- /dev/null +++ b/android/res/menu/options_main.xml @@ -0,0 +1,16 @@ + + + + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100755 index 00000000..8c78e88b --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,9 @@ + + + i2pd + i2pd started + Quit + Graceful Quit + Graceful quit is already in progress + Graceful quit is in progress + diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java new file mode 100644 index 00000000..6ff826c4 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -0,0 +1,88 @@ +package org.purplei2p.i2pd; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +public class ForegroundService extends Service { +// private NotificationManager mNM; + + // Unique Identification Number for the Notification. + // We use it on Notification start, and to cancel it. + private int NOTIFICATION = R.string.i2pd_started; + + /** + * Class for clients to access. Because we know this service always + * runs in the same process as its clients, we don't need to deal with + * IPC. + */ + public class LocalBinder extends Binder { + ForegroundService getService() { + return ForegroundService.this; + } + } + + @Override + public void onCreate() { +// mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + + // Display a notification about us starting. We put an icon in the status bar. + showNotification(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("ForegroundService", "Received start id " + startId + ": " + intent); + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + // Cancel the persistent notification. + //mNM.cancel(NOTIFICATION); + stopForeground(true); + + // Tell the user we stopped. + //Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + // This is the object that receives interactions from clients. See + // RemoteService for a more complete example. + private final IBinder mBinder = new LocalBinder(); + + /** + * Show a notification while this service is running. + */ + private void showNotification() { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(R.string.i2pd_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PD.class), 0); + + // Set the info for the views that show in the notification panel. + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.app_name)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + } +} \ No newline at end of file diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java new file mode 100755 index 00000000..ef22f941 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -0,0 +1,272 @@ +package org.purplei2p.i2pd; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Timer; +import java.util.TimerTask; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; +import android.widget.Toast; + +public class I2PD extends Activity { + private static final String TAG = "i2pd"; + + private static Throwable loadLibsThrowable; + static { + try { + I2PD_JNI.loadLibraries(); + } catch (Throwable tr) { + loadLibsThrowable = tr; + } + } + private String daemonStartResult="N/A"; + private boolean destroyed=false; + private TextView textView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + destroyed=false; + + //set the app be foreground (do not unload when RAM needed) + doBindService(); + + textView = new TextView(this); + setContentView(textView); + if (loadLibsThrowable != null) { + textView.setText(throwableToString(loadLibsThrowable)+"\r\n"); + return; + } + try { + textView.setText( + "libi2pd.so was compiled with ABI " + getABICompiledWith() + "\r\n"+ + "Starting daemon... "); + new Thread(new Runnable(){ + + @Override + public void run() { + try { + doStartDaemon(); + } catch (final Throwable tr) { + appendThrowable(tr); + } + } + + },"i2pdDaemonStarting").start(); + } catch (Throwable tr) { + textView.setText(textView.getText().toString()+throwableToString(tr)); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + localDestroy(); + } + + private synchronized void localDestroy() { + if(destroyed)return; + destroyed=true; + if(gracefulQuitTimer!=null) { + gracefulQuitTimer.cancel(); + gracefulQuitTimer = null; + } + try{ + doUnbindService(); + }catch(Throwable tr){ + Log.e(TAG, "", tr); + } + if("ok".equals(daemonStartResult)) { + try { + I2PD_JNI.stopDaemon(); + } catch (Throwable tr) { + Log.e(TAG, "error", tr); + } + } + } + + private CharSequence throwableToString(Throwable tr) { + StringWriter sw = new StringWriter(8192); + PrintWriter pw = new PrintWriter(sw); + tr.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + + public String getABICompiledWith() { + return I2PD_JNI.getABICompiledWith(); + } + + private synchronized void doStartDaemon() { + if(destroyed)return; + daemonStartResult = I2PD_JNI.startDaemon(); + runOnUiThread(new Runnable(){ + + @Override + public void run() { + synchronized (I2PD.this) { + if(destroyed)return; + textView.setText( + textView.getText().toString()+ + "start result: "+daemonStartResult+"\r\n" + ); + } + } + + }); + } + + private void appendThrowable(final Throwable tr) { + runOnUiThread(new Runnable(){ + + @Override + public void run() { + synchronized (I2PD.this) { + if(destroyed)return; + textView.setText(textView.getText().toString()+throwableToString(tr)+"\r\n"); + } + } + }); + } + +// private LocalService mBoundService; + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the service object we can use to + // interact with the service. Because we have bound to a explicit + // service that we know is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. +// mBoundService = ((LocalService.LocalBinder)service).getService(); + + // Tell the user about this for our demo. +// Toast.makeText(Binding.this, R.string.local_service_connected, +// Toast.LENGTH_SHORT).show(); + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + // Because it is running in our same process, we should never + // see this happen. +// mBoundService = null; +// Toast.makeText(Binding.this, R.string.local_service_disconnected, +// Toast.LENGTH_SHORT).show(); + } + }; + + + private boolean mIsBound; + + private void doBindService() { + // Establish a connection with the service. We use an explicit + // class name because we want a specific service implementation that + // we know will be running in our own process (and thus won't be + // supporting component replacement by other applications). + bindService(new Intent(this, + ForegroundService.class), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + private void doUnbindService() { + if (mIsBound) { + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.options_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + switch(id){ + case R.id.action_quit: + quit(); + return true; + case R.id.action_graceful_quit: + gracefulQuit(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @SuppressLint("NewApi") + private void quit() { + try { + localDestroy(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + finishAndRemoveTask(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + finishAffinity(); + } else { + //moveTaskToBack(true); + finish(); + } + }catch (Throwable tr) { + Log.e(TAG, "", tr); + } + } + + private Timer gracefulQuitTimer; + private synchronized void gracefulQuit() { + if(gracefulQuitTimer!=null){ + Toast.makeText(this, R.string.graceful_quit_is_already_in_progress, + Toast.LENGTH_SHORT).show(); + return; + } + Toast.makeText(this, R.string.graceful_quit_is_in_progress, + Toast.LENGTH_SHORT).show(); + new Thread(new Runnable(){ + + @Override + public void run() { + try{ + synchronized (I2PD.this) { + if("ok".equals(daemonStartResult)) { + I2PD_JNI.stopAcceptingTunnels(); + gracefulQuitTimer = new Timer(true); + gracefulQuitTimer.schedule(new TimerTask(){ + + @Override + public void run() { + quit(); + } + + }, 10*60*1000/*millis*/); + }else{ + quit(); + } + } + } catch(Throwable tr) { + Log.e(TAG,"",tr); + } + } + + },"gracQuitInit").start(); + } +} diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java new file mode 100644 index 00000000..4f5913e3 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -0,0 +1,21 @@ +package org.purplei2p.i2pd; + +public class I2PD_JNI { + public static native String getABICompiledWith(); + /** + * returns error info if failed + * returns "ok" if daemon initialized and started okay + */ + public static native String startDaemon(); + //should only be called after startDaemon() success + public static native void stopDaemon(); + + public static native void stopAcceptingTunnels(); + + public static native void onNetworkStateChanged(boolean isConnected); + + public static void loadLibraries() { + //System.loadLibrary("gnustl_shared"); + System.loadLibrary("i2pd"); + } +} diff --git a/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java new file mode 100644 index 00000000..e2f284b0 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java @@ -0,0 +1,30 @@ +package org.purplei2p.i2pd; + +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public class NetworkStateChangeReceiver extends BroadcastReceiver { + + private static final String TAG = "i2pd"; + + //api level 1 + @Override + public void onReceive(final Context context, final Intent intent) { + Log.d(TAG,"Network state change"); + try { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); + boolean isConnected = activeNetworkInfo!=null && activeNetworkInfo.isConnected(); + // https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html?hl=ru + // boolean isWiFi = activeNetworkInfo!=null && (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI); + + I2PD_JNI.onNetworkStateChanged(isConnected); + } catch (Throwable tr) { + Log.d(TAG,"",tr); + } + } +} diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 567f3d66..90ab6c10 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -14,7 +14,6 @@ MAIN_PATH = /path/to/libraries # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git - BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 0e2ca01c..1b8af253 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -11,8 +11,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, ui(new Ui::MainWindow)*/ #ifndef ANDROID - , - quitting(false) + ,quitting(false) #endif { //ui->setupUi(this);