R4SAS
4 years ago
commit
e41ddc65cd
42 changed files with 3431 additions and 0 deletions
@ -0,0 +1,18 @@ |
|||||||
|
gen |
||||||
|
tests |
||||||
|
bin |
||||||
|
libs |
||||||
|
log* |
||||||
|
obj |
||||||
|
.cxx |
||||||
|
.gradle |
||||||
|
.idea |
||||||
|
.externalNativeBuild |
||||||
|
ant.properties |
||||||
|
local.properties |
||||||
|
build.sh |
||||||
|
android.iml |
||||||
|
build |
||||||
|
*.iml |
||||||
|
*.local |
||||||
|
*.jks |
@ -0,0 +1,4 @@ |
|||||||
|
[submodule "i2pd"] |
||||||
|
path = app/jni/i2pd |
||||||
|
url = https://github.com/PurpleI2P/i2pd.git |
||||||
|
branch = openssl |
@ -0,0 +1,19 @@ |
|||||||
|
# how to compile? |
||||||
|
## Install the gradle + NDK or use android-studio |
||||||
|
[https://gradle.org/install/](https://gradle.org/install/) |
||||||
|
|
||||||
|
## Install the depencies |
||||||
|
``` |
||||||
|
git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git -b boost-1_72_0 |
||||||
|
git clone https://github.com/PurpleI2P/android-ifaddrs.git |
||||||
|
git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git |
||||||
|
git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git |
||||||
|
``` |
||||||
|
## Set libs in jni/Application.mk on 24 line: |
||||||
|
``` |
||||||
|
# change to your own |
||||||
|
I2PD_LIBS_PATH = /home/user/i2pd/android/ |
||||||
|
``` |
||||||
|
|
||||||
|
## compile apk file |
||||||
|
gradle clean assembleRelease |
@ -0,0 +1,87 @@ |
|||||||
|
plugins { |
||||||
|
id 'com.android.application' |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
implementation 'androidx.core:core:1.0.2' |
||||||
|
} |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdkVersion 29 |
||||||
|
buildToolsVersion "28.0.3" |
||||||
|
defaultConfig { |
||||||
|
applicationId "org.purplei2p.i2pd" |
||||||
|
targetSdkVersion 29 |
||||||
|
minSdkVersion 14 |
||||||
|
versionCode 2321 |
||||||
|
versionName "2.32.1" |
||||||
|
setProperty("archivesBaseName", archivesBaseName + "-" + versionName) |
||||||
|
ndk { |
||||||
|
abiFilters 'armeabi-v7a' |
||||||
|
abiFilters 'x86' |
||||||
|
//abiFilters 'arm64-v8a' |
||||||
|
//abiFilters 'x86_64' |
||||||
|
} |
||||||
|
externalNativeBuild { |
||||||
|
ndkBuild { |
||||||
|
arguments "-j3" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
sourceSets { |
||||||
|
main { |
||||||
|
manifest.srcFile 'AndroidManifest.xml' |
||||||
|
java.srcDirs = ['src'] |
||||||
|
res.srcDirs = ['res'] |
||||||
|
jniLibs.srcDirs = ['libs'] |
||||||
|
assets.srcDirs = ['assets'] |
||||||
|
} |
||||||
|
} |
||||||
|
splits { |
||||||
|
abi { |
||||||
|
// change that to true if you need splitted apk |
||||||
|
enable true |
||||||
|
reset() |
||||||
|
//include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" |
||||||
|
include "armeabi-v7a", "x86" |
||||||
|
universalApk true |
||||||
|
} |
||||||
|
} |
||||||
|
signingConfigs { |
||||||
|
orignal { |
||||||
|
storeFile file("i2pdapk.jks") |
||||||
|
storePassword "android" |
||||||
|
keyAlias "i2pdapk" |
||||||
|
keyPassword "android" |
||||||
|
} |
||||||
|
} |
||||||
|
buildTypes { |
||||||
|
release { |
||||||
|
minifyEnabled false |
||||||
|
signingConfig signingConfigs.orignal |
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' |
||||||
|
} |
||||||
|
} |
||||||
|
externalNativeBuild { |
||||||
|
ndkBuild { |
||||||
|
path './jni/Android.mk' |
||||||
|
} |
||||||
|
} |
||||||
|
compileOptions { |
||||||
|
sourceCompatibility = '1.8' |
||||||
|
targetCompatibility = '1.8' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ext.abiCodes = ['armeabi-v7a':1, 'x86':2, 'arm64-v8a':3, 'x86_64':4] |
||||||
|
import com.android.build.OutputFile |
||||||
|
|
||||||
|
android.applicationVariants.all { variant -> |
||||||
|
variant.outputs.each { output -> |
||||||
|
def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) |
||||||
|
|
||||||
|
if (baseAbiVersionCode != null) { |
||||||
|
output.versionCodeOverride = baseAbiVersionCode + variant.versionCode |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
LOCAL_PATH := $(call my-dir) |
||||||
|
include $(CLEAR_VARS) |
||||||
|
LOCAL_MODULE := i2pd |
||||||
|
LOCAL_CPP_FEATURES := rtti exceptions |
||||||
|
LOCAL_C_INCLUDES += $(IFADDRS_PATH) $(LIB_SRC_PATH) $(LIB_CLIENT_SRC_PATH) $(DAEMON_SRC_PATH) |
||||||
|
LOCAL_STATIC_LIBRARIES := \
|
||||||
|
boost_system \
|
||||||
|
boost_date_time \
|
||||||
|
boost_filesystem \
|
||||||
|
boost_program_options \
|
||||||
|
crypto \
|
||||||
|
ssl \
|
||||||
|
miniupnpc |
||||||
|
LOCAL_LDLIBS := -lz |
||||||
|
|
||||||
|
LOCAL_SRC_FILES := \
|
||||||
|
DaemonAndroid.cpp \
|
||||||
|
i2pd_android.cpp \
|
||||||
|
$(IFADDRS_PATH)/ifaddrs.c \
|
||||||
|
$(wildcard $(LIB_SRC_PATH)/*.cpp) \
|
||||||
|
$(wildcard $(LIB_CLIENT_SRC_PATH)/*.cpp) \
|
||||||
|
$(DAEMON_SRC_PATH)/Daemon.cpp \
|
||||||
|
$(DAEMON_SRC_PATH)/UPnP.cpp \
|
||||||
|
$(DAEMON_SRC_PATH)/HTTPServer.cpp \
|
||||||
|
$(DAEMON_SRC_PATH)/I2PControl.cpp |
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY) |
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir) |
||||||
|
include $(CLEAR_VARS) |
||||||
|
LOCAL_MODULE := boost_system |
||||||
|
LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_system.a |
||||||
|
LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include |
||||||
|
include $(PREBUILT_STATIC_LIBRARY) |
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir) |
||||||
|
include $(CLEAR_VARS) |
||||||
|
LOCAL_MODULE := boost_date_time |
||||||
|
LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time.a |
||||||
|
LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include |
||||||
|
include $(PREBUILT_STATIC_LIBRARY) |
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir) |
||||||
|
include $(CLEAR_VARS) |
||||||
|
LOCAL_MODULE := boost_filesystem |
||||||
|
LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem.a |
||||||
|
LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include |
||||||
|
include $(PREBUILT_STATIC_LIBRARY) |
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir) |
||||||
|
include $(CLEAR_VARS) |
||||||
|
LOCAL_MODULE := boost_program_options |
||||||
|
LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options.a |
||||||
|
LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include |
||||||
|
include $(PREBUILT_STATIC_LIBRARY) |
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir) |
||||||
|
include $(CLEAR_VARS) |
||||||
|
LOCAL_MODULE := crypto |
||||||
|
LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libcrypto.a |
||||||
|
LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/include |
||||||
|
include $(PREBUILT_STATIC_LIBRARY) |
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir) |
||||||
|
include $(CLEAR_VARS) |
||||||
|
LOCAL_MODULE := ssl |
||||||
|
LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libssl.a |
||||||
|
LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/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)/miniupnpc-2.1/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a |
||||||
|
LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnpc-2.1/include |
||||||
|
include $(PREBUILT_STATIC_LIBRARY) |
@ -0,0 +1,36 @@ |
|||||||
|
#APP_ABI := armeabi-v7a x86
|
||||||
|
#APP_PLATFORM := android-14
|
||||||
|
|
||||||
|
# ABI arm64-v8a and x86_64 supported only from platform-21
|
||||||
|
#APP_ABI := arm64-v8a x86_64
|
||||||
|
#APP_PLATFORM := android-21
|
||||||
|
|
||||||
|
NDK_TOOLCHAIN_VERSION := clang |
||||||
|
#APP_STL := c++_shared
|
||||||
|
APP_STL := c++_static |
||||||
|
|
||||||
|
# Enable c++17 extensions in source code
|
||||||
|
APP_CPPFLAGS += -std=c++17 -fexceptions -frtti |
||||||
|
|
||||||
|
APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP |
||||||
|
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) |
||||||
|
APP_CPPFLAGS += -DANDROID_ARM7A |
||||||
|
endif |
||||||
|
|
||||||
|
# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git -b boost-1_72_0
|
||||||
|
# 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 |
||||||
|
|
||||||
|
# don't change me
|
||||||
|
I2PD_SRC_PATH = $(PWD)/.. |
||||||
|
|
||||||
|
LIB_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd |
||||||
|
LIB_CLIENT_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd_client |
||||||
|
DAEMON_SRC_PATH = $(I2PD_SRC_PATH)/daemon |
@ -0,0 +1,222 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2020, The PurpleI2P Project |
||||||
|
* |
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3 |
||||||
|
* |
||||||
|
* See full license text in LICENSE file at top of project tree |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <iostream> |
||||||
|
#include <chrono> |
||||||
|
#include <thread> |
||||||
|
#include <exception> |
||||||
|
#include <boost/exception/diagnostic_information.hpp> |
||||||
|
#include <boost/exception_ptr.hpp> |
||||||
|
//#include "mainwindow.h"
|
||||||
|
#include "FS.h" |
||||||
|
#include "DaemonAndroid.h" |
||||||
|
#include "Daemon.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."); |
||||||
|
} |
||||||
|
} |
||||||
|
*/ |
||||||
|
std::string dataDir = ""; |
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// make sure assets are ready before proceed
|
||||||
|
i2p::fs::DetectDataDir(dataDir, false); |
||||||
|
int numAttempts = 0; |
||||||
|
do |
||||||
|
{ |
||||||
|
if (i2p::fs::Exists (i2p::fs::DataDirPath("assets.ready"))) break; // assets ready
|
||||||
|
numAttempts++; |
||||||
|
std::this_thread::sleep_for (std::chrono::seconds(1)); // otherwise wait for 1 more second
|
||||||
|
} |
||||||
|
while (numAttempts <= 10); // 10 seconds max
|
||||||
|
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(); |
||||||
|
} |
||||||
|
|
||||||
|
void SetDataDir(std::string jdataDir) |
||||||
|
{ |
||||||
|
dataDir = jdataDir; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2020, The PurpleI2P Project |
||||||
|
* |
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3 |
||||||
|
* |
||||||
|
* See full license text in LICENSE file at top of project tree |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef DAEMON_ANDROID_H |
||||||
|
#define DAEMON_ANDROID_H |
||||||
|
|
||||||
|
#include <string> |
||||||
|
|
||||||
|
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(); |
||||||
|
|
||||||
|
// set datadir received from jni
|
||||||
|
void SetDataDir(std::string jdataDir); |
||||||
|
/*
|
||||||
|
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
|
@ -0,0 +1,114 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2020, The PurpleI2P Project |
||||||
|
* |
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3 |
||||||
|
* |
||||||
|
* See full license text in LICENSE file at top of project tree |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <jni.h> |
||||||
|
#include "org_purplei2p_i2pd_I2PD_JNI.h" |
||||||
|
#include "DaemonAndroid.h" |
||||||
|
#include "RouterContext.h" |
||||||
|
#include "ClientContext.h" |
||||||
|
#include "Transports.h" |
||||||
|
#include "Tunnel.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_startAcceptingTunnels |
||||||
|
(JNIEnv *env, jclass clazz) { |
||||||
|
i2p::context.SetAcceptsTunnels (true); |
||||||
|
} |
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs |
||||||
|
(JNIEnv *env, jclass clazz) { |
||||||
|
i2p::client::context.ReloadConfig(); |
||||||
|
} |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir |
||||||
|
(JNIEnv *env, jclass clazz, jstring jdataDir) { |
||||||
|
|
||||||
|
/*
|
||||||
|
// Method 1: convert UTF-16 jstring to std::string (https://stackoverflow.com/a/41820336)
|
||||||
|
const jclass stringClass = env->GetObjectClass(jdataDir); |
||||||
|
const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); |
||||||
|
const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jdataDir, getBytes, env->NewStringUTF("UTF-8")); |
||||||
|
|
||||||
|
size_t length = (size_t) env->GetArrayLength(stringJbytes); |
||||||
|
jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL); |
||||||
|
|
||||||
|
std::string dataDir = std::string((char *)pBytes, length); |
||||||
|
env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); |
||||||
|
|
||||||
|
env->DeleteLocalRef(stringJbytes); |
||||||
|
env->DeleteLocalRef(stringClass); */ |
||||||
|
|
||||||
|
// Method 2: get string chars and make char array.
|
||||||
|
auto dataDir = env->GetStringUTFChars(jdataDir, NULL); |
||||||
|
env->ReleaseStringUTFChars(jdataDir, dataDir); |
||||||
|
|
||||||
|
// Set DataDir
|
||||||
|
i2p::android::SetDataDir(dataDir); |
||||||
|
} |
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount |
||||||
|
(JNIEnv *env, jclass clazz) { |
||||||
|
return i2p::tunnel::tunnels.CountTransitTunnels(); |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2020, The PurpleI2P Project |
||||||
|
* |
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3 |
||||||
|
* |
||||||
|
* See full license text in LICENSE file at top of project tree |
||||||
|
*/ |
||||||
|
|
||||||
|
/* 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_startAcceptingTunnels |
||||||
|
(JNIEnv *, jclass); |
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs |
||||||
|
(JNIEnv *, jclass); |
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged |
||||||
|
(JNIEnv * env, jclass clazz, jboolean isConnected); |
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir |
||||||
|
(JNIEnv *env, jclass clazz, jstring jdataDir); |
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount |
||||||
|
(JNIEnv *, jclass); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
||||||
|
#endif |
@ -0,0 +1,55 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
package="org.purplei2p.i2pd" |
||||||
|
android:installLocation="auto"> |
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
||||||
|
<uses-permission android:name="android.permission.INTERNET" /> |
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> |
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> |
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> |
||||||
|
|
||||||
|
<application |
||||||
|
android:allowBackup="true" |
||||||
|
android:icon="@drawable/icon" |
||||||
|
android:label="@string/app_name" |
||||||
|
android:theme="@android:style/Theme.Holo.Light.DarkActionBar" |
||||||
|
android:requestLegacyExternalStorage="true" |
||||||
|
android:usesCleartextTraffic="true" |
||||||
|
> |
||||||
|
|
||||||
|
<receiver android:name=".NetworkStateChangeReceiver"> |
||||||
|
<intent-filter> |
||||||
|
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> |
||||||
|
</intent-filter> |
||||||
|
</receiver> |
||||||
|
|
||||||
|
<activity |
||||||
|
android:name=".I2PDPermsAskerActivity" |
||||||
|
android:label="@string/app_name"> |
||||||
|
<intent-filter> |
||||||
|
<action android:name="android.intent.action.MAIN" /> |
||||||
|
<category android:name="android.intent.category.LAUNCHER" /> |
||||||
|
</intent-filter> |
||||||
|
</activity> |
||||||
|
|
||||||
|
<activity |
||||||
|
android:name=".I2PDActivity" |
||||||
|
android:label="@string/app_name" /> |
||||||
|
|
||||||
|
<service |
||||||
|
android:name=".ForegroundService" |
||||||
|
android:enabled="true" /> |
||||||
|
|
||||||
|
<activity |
||||||
|
android:name=".I2PDPermsExplanationActivity" |
||||||
|
android:label="@string/title_activity_i2_pdperms_asker_prompt" |
||||||
|
android:parentActivityName=".I2PDPermsAskerActivity"> |
||||||
|
<meta-data |
||||||
|
android:name="android.support.PARENT_ACTIVITY" |
||||||
|
android:value="org.purplei2p.i2pd.I2PDPermsAskerActivity" /> |
||||||
|
</activity> |
||||||
|
</application> |
||||||
|
</manifest> |
@ -0,0 +1,92 @@ |
|||||||
|
## Configuration file for a typical i2pd user |
||||||
|
## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ |
||||||
|
## for more options you can use in this file. |
||||||
|
|
||||||
|
#logfile = /sdcard/i2pd/i2pd.log |
||||||
|
loglevel = none |
||||||
|
#tunnelsdir = /sdcard/i2pd/tunnels.d |
||||||
|
|
||||||
|
# host = 1.2.3.4 |
||||||
|
# port = 4567 |
||||||
|
|
||||||
|
ipv4 = true |
||||||
|
ipv6 = false |
||||||
|
|
||||||
|
# ntcp = true |
||||||
|
# ntcpproxy = http://127.0.0.1:8118 |
||||||
|
# ssu = true |
||||||
|
|
||||||
|
bandwidth = L |
||||||
|
# share = 100 |
||||||
|
|
||||||
|
# notransit = true |
||||||
|
# floodfill = true |
||||||
|
|
||||||
|
[ntcp2] |
||||||
|
enabled = true |
||||||
|
|
||||||
|
[http] |
||||||
|
enabled = true |
||||||
|
address = 127.0.0.1 |
||||||
|
port = 7070 |
||||||
|
# auth = true |
||||||
|
# user = i2pd |
||||||
|
# pass = changeme |
||||||
|
|
||||||
|
[httpproxy] |
||||||
|
enabled = true |
||||||
|
address = 127.0.0.1 |
||||||
|
port = 4444 |
||||||
|
inbound.length = 1 |
||||||
|
inbound.quantity = 5 |
||||||
|
outbound.length = 1 |
||||||
|
outbound.quantity = 5 |
||||||
|
signaturetype=7 |
||||||
|
i2cp.leaseSetType=3 |
||||||
|
i2cp.leaseSetEncType=0,4 |
||||||
|
keys = proxy-keys.dat |
||||||
|
# addresshelper = true |
||||||
|
# outproxy = http://false.i2p |
||||||
|
## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. |
||||||
|
|
||||||
|
[socksproxy] |
||||||
|
enabled = true |
||||||
|
address = 127.0.0.1 |
||||||
|
port = 4447 |
||||||
|
keys = proxy-keys.dat |
||||||
|
# outproxy.enabled = false |
||||||
|
# outproxy = 127.0.0.1 |
||||||
|
# outproxyport = 9050 |
||||||
|
## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. |
||||||
|
|
||||||
|
[sam] |
||||||
|
enabled = false |
||||||
|
# address = 127.0.0.1 |
||||||
|
# port = 7656 |
||||||
|
|
||||||
|
[precomputation] |
||||||
|
elgamal = true |
||||||
|
|
||||||
|
[upnp] |
||||||
|
enabled = true |
||||||
|
# name = I2Pd |
||||||
|
|
||||||
|
[reseed] |
||||||
|
verify = true |
||||||
|
## Path to local reseed data file (.su3) for manual reseeding |
||||||
|
# file = /path/to/i2pseeds.su3 |
||||||
|
## or HTTPS URL to reseed from |
||||||
|
# file = https://legit-website.com/i2pseeds.su3 |
||||||
|
## Path to local ZIP file or HTTPS URL to reseed from |
||||||
|
# zipfile = /path/to/netDb.zip |
||||||
|
## If you run i2pd behind a proxy server, set proxy server for reseeding here |
||||||
|
## Should be http://address:port or socks://address:port |
||||||
|
# proxy = http://127.0.0.1:8118 |
||||||
|
## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default |
||||||
|
# threshold = 25 |
||||||
|
|
||||||
|
[limits] |
||||||
|
transittunnels = 50 |
||||||
|
|
||||||
|
[persist] |
||||||
|
profiles = false |
@ -0,0 +1,3 @@ |
|||||||
|
http://inr.i2p/export/alive-hosts.txt |
||||||
|
http://stats.i2p/cgi-bin/newhosts.txt |
||||||
|
http://i2p-projekt.i2p/hosts.txt |
@ -0,0 +1,33 @@ |
|||||||
|
[IRC-IRC2P] |
||||||
|
#type = client |
||||||
|
#address = 127.0.0.1 |
||||||
|
#port = 6668 |
||||||
|
#destination = irc.postman.i2p |
||||||
|
#destinationport = 6667 |
||||||
|
#keys = irc-keys.dat |
||||||
|
|
||||||
|
#[IRC-ILITA] |
||||||
|
#type = client |
||||||
|
#address = 127.0.0.1 |
||||||
|
#port = 6669 |
||||||
|
#destination = irc.ilita.i2p |
||||||
|
#destinationport = 6667 |
||||||
|
#keys = irc-keys.dat |
||||||
|
|
||||||
|
#[SMTP] |
||||||
|
#type = client |
||||||
|
#address = 127.0.0.1 |
||||||
|
#port = 7659 |
||||||
|
#destination = smtp.postman.i2p |
||||||
|
#destinationport = 25 |
||||||
|
#keys = smtp-keys.dat |
||||||
|
|
||||||
|
#[POP3] |
||||||
|
#type = client |
||||||
|
#address = 127.0.0.1 |
||||||
|
#port = 7660 |
||||||
|
#destination = pop.postman.i2p |
||||||
|
#destinationport = 110 |
||||||
|
#keys = pop3-keys.dat |
||||||
|
|
||||||
|
# see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ |
@ -0,0 +1,181 @@ |
|||||||
|
package org.purplei2p.i2pd; |
||||||
|
|
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Set; |
||||||
|
import android.os.Environment; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import org.purplei2p.i2pd.R; |
||||||
|
|
||||||
|
public class DaemonSingleton { |
||||||
|
private static final String TAG = "i2pd"; |
||||||
|
private static final DaemonSingleton instance = new DaemonSingleton(); |
||||||
|
|
||||||
|
public interface StateUpdateListener { |
||||||
|
void daemonStateUpdate(); |
||||||
|
} |
||||||
|
|
||||||
|
private final Set<StateUpdateListener> stateUpdateListeners = new HashSet<>(); |
||||||
|
|
||||||
|
public static DaemonSingleton getInstance() { |
||||||
|
return instance; |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void addStateChangeListener(StateUpdateListener listener) { |
||||||
|
stateUpdateListeners.add(listener); |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void removeStateChangeListener(StateUpdateListener listener) { |
||||||
|
stateUpdateListeners.remove(listener); |
||||||
|
} |
||||||
|
|
||||||
|
private synchronized void setState(State newState) { |
||||||
|
if (newState == null) |
||||||
|
throw new NullPointerException(); |
||||||
|
|
||||||
|
State oldState = state; |
||||||
|
|
||||||
|
if (oldState == null) |
||||||
|
throw new NullPointerException(); |
||||||
|
|
||||||
|
if (oldState.equals(newState)) |
||||||
|
return; |
||||||
|
|
||||||
|
state = newState; |
||||||
|
fireStateUpdate1(); |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void stopAcceptingTunnels() { |
||||||
|
if (isStartedOkay()) { |
||||||
|
setState(State.gracefulShutdownInProgress); |
||||||
|
I2PD_JNI.stopAcceptingTunnels(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void startAcceptingTunnels() { |
||||||
|
if (isStartedOkay()) { |
||||||
|
setState(State.startedOkay); |
||||||
|
I2PD_JNI.startAcceptingTunnels(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void reloadTunnelsConfigs() { |
||||||
|
if (isStartedOkay()) { |
||||||
|
I2PD_JNI.reloadTunnelsConfigs(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized int GetTransitTunnelsCount() { |
||||||
|
return I2PD_JNI.GetTransitTunnelsCount(); |
||||||
|
} |
||||||
|
|
||||||
|
private volatile boolean startedOkay; |
||||||
|
|
||||||
|
public enum State { |
||||||
|
uninitialized(R.string.uninitialized), |
||||||
|
starting(R.string.starting), |
||||||
|
jniLibraryLoaded(R.string.jniLibraryLoaded), |
||||||
|
startedOkay(R.string.startedOkay), |
||||||
|
startFailed(R.string.startFailed), |
||||||
|
gracefulShutdownInProgress(R.string.gracefulShutdownInProgress), |
||||||
|
stopped(R.string.stopped); |
||||||
|
|
||||||
|
State(int statusStringResourceId) { |
||||||
|
this.statusStringResourceId = statusStringResourceId; |
||||||
|
} |
||||||
|
|
||||||
|
private final int statusStringResourceId; |
||||||
|
|
||||||
|
public int getStatusStringResourceId() { |
||||||
|
return statusStringResourceId; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
private volatile State state = State.uninitialized; |
||||||
|
|
||||||
|
public State getState() { |
||||||
|
return state; |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
setState(State.starting); |
||||||
|
new Thread(new Runnable() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
try { |
||||||
|
I2PD_JNI.loadLibraries(); |
||||||
|
setState(State.jniLibraryLoaded); |
||||||
|
} catch (Throwable tr) { |
||||||
|
lastThrowable = tr; |
||||||
|
setState(State.startFailed); |
||||||
|
return; |
||||||
|
} |
||||||
|
try { |
||||||
|
synchronized (DaemonSingleton.this) { |
||||||
|
I2PD_JNI.setDataDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd"); |
||||||
|
daemonStartResult = I2PD_JNI.startDaemon(); |
||||||
|
if ("ok".equals(daemonStartResult)) { |
||||||
|
setState(State.startedOkay); |
||||||
|
setStartedOkay(true); |
||||||
|
} else |
||||||
|
setState(State.startFailed); |
||||||
|
} |
||||||
|
} catch (Throwable tr) { |
||||||
|
lastThrowable = tr; |
||||||
|
setState(State.startFailed); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}, "i2pdDaemonStart").start(); |
||||||
|
} |
||||||
|
|
||||||
|
private Throwable lastThrowable; |
||||||
|
private String daemonStartResult = "N/A"; |
||||||
|
|
||||||
|
private void fireStateUpdate1() { |
||||||
|
Log.i(TAG, "daemon state change: " + state); |
||||||
|
for (StateUpdateListener listener : stateUpdateListeners) { |
||||||
|
try { |
||||||
|
listener.daemonStateUpdate(); |
||||||
|
} catch (Throwable tr) { |
||||||
|
Log.e(TAG, "exception in listener ignored", tr); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Throwable getLastThrowable() { |
||||||
|
return lastThrowable; |
||||||
|
} |
||||||
|
|
||||||
|
public String getDaemonStartResult() { |
||||||
|
return daemonStartResult; |
||||||
|
} |
||||||
|
|
||||||
|
private final Object startedOkayLock = new Object(); |
||||||
|
|
||||||
|
public boolean isStartedOkay() { |
||||||
|
synchronized (startedOkayLock) { |
||||||
|
return startedOkay; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void setStartedOkay(boolean startedOkay) { |
||||||
|
synchronized (startedOkayLock) { |
||||||
|
this.startedOkay = startedOkay; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void stopDaemon() { |
||||||
|
if (isStartedOkay()) { |
||||||
|
try { |
||||||
|
I2PD_JNI.stopDaemon(); |
||||||
|
} catch(Throwable tr) { |
||||||
|
Log.e(TAG, "", tr); |
||||||
|
} |
||||||
|
|
||||||
|
setStartedOkay(false); |
||||||
|
setState(State.stopped); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,149 @@ |
|||||||
|
package org.purplei2p.i2pd; |
||||||
|
|
||||||
|
import android.app.Notification; |
||||||
|
import android.app.NotificationChannel; |
||||||
|
import android.app.NotificationManager; |
||||||
|
import android.app.PendingIntent; |
||||||
|
import android.app.Service; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Binder; |
||||||
|
import android.os.Build; |
||||||
|
import android.os.IBinder; |
||||||
|
import androidx.annotation.RequiresApi; |
||||||
|
import androidx.core.app.NotificationCompat; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
public class ForegroundService extends Service { |
||||||
|
private static final String TAG="FgService"; |
||||||
|
|
||||||
|
private volatile boolean shown; |
||||||
|
|
||||||
|
private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = |
||||||
|
new DaemonSingleton.StateUpdateListener() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void daemonStateUpdate() { |
||||||
|
try { |
||||||
|
synchronized (ForegroundService.this) { |
||||||
|
if (shown) cancelNotification(); |
||||||
|
showNotification(); |
||||||
|
} |
||||||
|
} catch (Throwable tr) { |
||||||
|
Log.e(TAG,"error ignored",tr); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
private NotificationManager notificationManager; |
||||||
|
|
||||||
|
// Unique Identification Number for the Notification.
|
||||||
|
// We use it on Notification start, and to cancel it.
|
||||||
|
private int NOTIFICATION = 1; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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() { |
||||||
|
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); |
||||||
|
|
||||||
|
synchronized (this) { |
||||||
|
DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); |
||||||
|
if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); |
||||||
|
} |
||||||
|
// Tell the user we started.
|
||||||
|
// Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show();
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) { |
||||||
|
Log.i("ForegroundService", "Received start id " + startId + ": " + intent); |
||||||
|
return START_STICKY; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDestroy() { |
||||||
|
DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); |
||||||
|
cancelNotification(); |
||||||
|
} |
||||||
|
|
||||||
|
private synchronized void cancelNotification() { |
||||||
|
// Cancel the persistent notification.
|
||||||
|
notificationManager.cancel(NOTIFICATION); |
||||||
|
|
||||||
|
stopForeground(true); |
||||||
|
|
||||||
|
// Tell the user we stopped.
|
||||||
|
//Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show();
|
||||||
|
shown=false; |
||||||
|
} |
||||||
|
|
||||||
|
@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 synchronized void showNotification() { |
||||||
|
// In this sample, we'll use the same text for the ticker and the expanded notification
|
||||||
|
CharSequence text = getText(DaemonSingleton.getInstance().getState().getStatusStringResourceId()); |
||||||
|
|
||||||
|
// The PendingIntent to launch our activity if the user selects this notification
|
||||||
|
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, |
||||||
|
new Intent(this, I2PDActivity.class), 0); |
||||||
|
|
||||||
|
// If earlier version channel ID is not used
|
||||||
|
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||||
|
String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; |
||||||
|
|
||||||
|
// Set the info for the views that show in the notification panel.
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) |
||||||
|
.setOngoing(true) |
||||||
|
.setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon
|
||||||
|
if(Build.VERSION.SDK_INT >= 16) builder = builder.setPriority(Notification.PRIORITY_DEFAULT); |
||||||
|
if(Build.VERSION.SDK_INT >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE); |
||||||
|
Notification notification = builder |
||||||
|
.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); |
||||||
|
shown = true; |
||||||
|
} |
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O) |
||||||
|
private synchronized String createNotificationChannel() { |
||||||
|
String channelId = getString(R.string.app_name); |
||||||
|
CharSequence channelName = "I2Pd service"; |
||||||
|
NotificationChannel chan = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW); |
||||||
|
//chan.setLightColor(Color.PURPLE);
|
||||||
|
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); |
||||||
|
NotificationManager service = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); |
||||||
|
if(service!=null)service.createNotificationChannel(chan); |
||||||
|
else Log.e(TAG, "error: NOTIFICATION_SERVICE is null"); |
||||||
|
return channelId; |
||||||
|
} |
||||||
|
|
||||||
|
private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); |
||||||
|
} |
@ -0,0 +1,654 @@ |
|||||||
|
package org.purplei2p.i2pd; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FileReader; |
||||||
|
import java.io.FileWriter; |
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.OutputStream; |
||||||
|
import java.io.PrintWriter; |
||||||
|
import java.io.StringWriter; |
||||||
|
import java.util.Timer; |
||||||
|
import java.util.TimerTask; |
||||||
|
|
||||||
|
import android.Manifest; |
||||||
|
import android.annotation.SuppressLint; |
||||||
|
import android.app.Activity; |
||||||
|
import android.app.AlertDialog; |
||||||
|
import android.content.ActivityNotFoundException; |
||||||
|
import android.content.ComponentName; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.Intent; |
||||||
|
import android.content.ServiceConnection; |
||||||
|
import android.content.SharedPreferences; |
||||||
|
import android.content.res.AssetManager; |
||||||
|
import android.content.pm.PackageManager; |
||||||
|
import android.net.Uri; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.os.Build; |
||||||
|
import android.os.Environment; |
||||||
|
import android.os.IBinder; |
||||||
|
import android.os.PowerManager; |
||||||
|
import android.preference.PreferenceManager; |
||||||
|
import android.provider.Settings; |
||||||
|
import android.util.Log; |
||||||
|
import android.view.Menu; |
||||||
|
import android.view.MenuItem; |
||||||
|
import android.widget.TextView; |
||||||
|
import android.widget.Toast; |
||||||
|
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.core.app.ActivityCompat; |
||||||
|
import androidx.core.content.ContextCompat; |
||||||
|
|
||||||
|
// For future package update checking
|
||||||
|
|
||||||
|
import android.webkit.WebSettings; |
||||||
|
import android.webkit.WebView; |
||||||
|
import android.webkit.WebViewClient; |
||||||
|
|
||||||
|
|
||||||
|
import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS; |
||||||
|
|
||||||
|
public class I2PDActivity extends Activity { |
||||||
|
private WebView webView; |
||||||
|
|
||||||
|
private static final String TAG = "i2pdActvt"; |
||||||
|
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1; |
||||||
|
public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000; |
||||||
|
public static final String PACKAGE_URI_SCHEME = "package:"; |
||||||
|
|
||||||
|
private TextView textView; |
||||||
|
private boolean assetsCopied; |
||||||
|
private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/"; |
||||||
|
//private ConfigParser parser = new ConfigParser(i2pdpath); // TODO:
|
||||||
|
|
||||||
|
private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); |
||||||
|
|
||||||
|
private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = new DaemonSingleton.StateUpdateListener() { |
||||||
|
@Override |
||||||
|
public void daemonStateUpdate() { |
||||||
|
processAssets(); |
||||||
|
runOnUiThread(() -> { |
||||||
|
try { |
||||||
|
if (textView == null) |
||||||
|
return; |
||||||
|
Throwable tr = daemon.getLastThrowable(); |
||||||
|
if (tr!=null) { |
||||||
|
textView.setText(throwableToString(tr)); |
||||||
|
return; |
||||||
|
} |
||||||
|
DaemonSingleton.State state = daemon.getState(); |
||||||
|
String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; |
||||||
|
String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; |
||||||
|
textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr)); |
||||||
|
} catch (Throwable tr) { |
||||||
|
Log.e(TAG,"error ignored",tr); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
private static volatile long graceStartedMillis; |
||||||
|
private static final Object graceStartedMillis_LOCK = new Object(); |
||||||
|
private Menu optionsMenu; |
||||||
|
|
||||||
|
private static String formatGraceTimeRemaining() { |
||||||
|
long remainingSeconds; |
||||||
|
synchronized (graceStartedMillis_LOCK) { |
||||||
|
remainingSeconds = Math.round(Math.max(0, graceStartedMillis + GRACEFUL_DELAY_MILLIS - System.currentTimeMillis()) / 1000.0D); |
||||||
|
} |
||||||
|
long remainingMinutes = (long)Math.floor(remainingSeconds / 60.0D); |
||||||
|
long remSec = remainingSeconds - remainingMinutes * 60; |
||||||
|
return remainingMinutes + ":" + (remSec / 10) + remSec % 10; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate(Bundle savedInstanceState) { |
||||||
|
Log.i(TAG, "onCreate"); |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
|
||||||
|
textView = new TextView(this); |
||||||
|
setContentView(textView); |
||||||
|
daemon.addStateChangeListener(daemonStateUpdatedListener); |
||||||
|
daemonStateUpdatedListener.daemonStateUpdate(); |
||||||
|
|
||||||
|
// request permissions
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) { |
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { |
||||||
|
ActivityCompat.requestPermissions(this, |
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, |
||||||
|
MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// set the app be foreground
|
||||||
|
doBindService(); |
||||||
|
|
||||||
|
final Timer gracefulQuitTimer = getGracefulQuitTimer(); |
||||||
|
if (gracefulQuitTimer != null) { |
||||||
|
long gracefulStopAtMillis; |
||||||
|
synchronized (graceStartedMillis_LOCK) { |
||||||
|
gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS; |
||||||
|
} |
||||||
|
rescheduleGraceStop(gracefulQuitTimer, gracefulStopAtMillis); |
||||||
|
} |
||||||
|
|
||||||
|
openBatteryOptimizationDialogIfNeeded(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onDestroy() { |
||||||
|
super.onDestroy(); |
||||||
|
textView = null; |
||||||
|
daemon.removeStateChangeListener(daemonStateUpdatedListener); |
||||||
|
//cancelGracefulStop0();
|
||||||
|
try { |
||||||
|
doUnbindService(); |
||||||
|
} catch(Throwable tr) { |
||||||
|
Log.e(TAG, "", tr); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) |
||||||
|
{ |
||||||
|
if (requestCode == MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) { |
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) |
||||||
|
Log.e(TAG, "WR_EXT_STORAGE perm granted"); |
||||||
|
else { |
||||||
|
Log.e(TAG, "WR_EXT_STORAGE perm declined, stopping i2pd"); |
||||||
|
i2pdStop(); |
||||||
|
//TODO must work w/o this perm, ask orignal
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void cancelGracefulStop0() { |
||||||
|
Timer gracefulQuitTimer = getGracefulQuitTimer(); |
||||||
|
if (gracefulQuitTimer != null) { |
||||||
|
gracefulQuitTimer.cancel(); |
||||||
|
setGracefulQuitTimer(null); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private CharSequence throwableToString(Throwable tr) { |
||||||
|
StringWriter sw = new StringWriter(8192); |
||||||
|
PrintWriter pw = new PrintWriter(sw); |
||||||
|
tr.printStackTrace(pw); |
||||||
|
pw.close(); |
||||||
|
return sw.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
// 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 static volatile boolean mIsBound; |
||||||
|
|
||||||
|
private void doBindService() { |
||||||
|
synchronized (I2PDActivity.class) { |
||||||
|
if (mIsBound) |
||||||
|
return; |
||||||
|
// 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() { |
||||||
|
synchronized (I2PDActivity.class) { |
||||||
|
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); |
||||||
|
menu.findItem(R.id.action_battery_otimizations).setVisible(isBatteryOptimizationsOpenOsDialogApiAvailable()); |
||||||
|
this.optionsMenu = menu; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isBatteryOptimizationsOpenOsDialogApiAvailable() { |
||||||
|
return android.os.Build.VERSION.SDK_INT >= 23; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onOptionsItemSelected(@NonNull 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_stop: |
||||||
|
i2pdStop(); |
||||||
|
return true; |
||||||
|
|
||||||
|
case R.id.action_graceful_stop: |
||||||
|
synchronized (graceStartedMillis_LOCK) { |
||||||
|
if (getGracefulQuitTimer() != null) |
||||||
|
cancelGracefulStop(); |
||||||
|
else |
||||||
|
i2pdGracefulStop(); |
||||||
|
} |
||||||
|
return true; |
||||||
|
|
||||||
|
case R.id.action_battery_otimizations: |
||||||
|
onActionBatteryOptimizations(); |
||||||
|
return true; |
||||||
|
|
||||||
|
case R.id.action_reload_tunnels_config: |
||||||
|
onReloadTunnelsConfig(); |
||||||
|
return true; |
||||||
|
|
||||||
|
case R.id.action_start_webview: |
||||||
|
setContentView(R.layout.webview); |
||||||
|
this.webView = (WebView) findViewById(R.id.webview1); |
||||||
|
this.webView.setWebViewClient(new WebViewClient()); |
||||||
|
|
||||||
|
WebSettings webSettings = this.webView.getSettings(); |
||||||
|
webSettings.setBuiltInZoomControls(true); |
||||||
|
webSettings.setJavaScriptEnabled(false); |
||||||
|
this.webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item); |
||||||
|
} |
||||||
|
|
||||||
|
private void onActionBatteryOptimizations() { |
||||||
|
if (isBatteryOptimizationsOpenOsDialogApiAvailable()) { |
||||||
|
try { |
||||||
|
startActivity(new Intent(ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)); |
||||||
|
} catch (ActivityNotFoundException e) { |
||||||
|
Log.e(TAG, "BATT_OPTIM_DIALOG_ActvtNotFound", e); |
||||||
|
Toast.makeText(this, R.string.os_version_does_not_support_battery_optimizations_show_os_dialog_api, Toast.LENGTH_SHORT).show(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void onReloadTunnelsConfig() { |
||||||
|
Log.d(TAG, "reloading tunnels"); |
||||||
|
daemon.reloadTunnelsConfigs(); |
||||||
|
Toast.makeText(this, R.string.tunnels_reloading, Toast.LENGTH_SHORT).show(); |
||||||
|
} |
||||||
|
|
||||||
|
private void i2pdStop() { |
||||||
|
cancelGracefulStop0(); |
||||||
|
new Thread(() -> { |
||||||
|
Log.d(TAG, "stopping"); |
||||||
|
try { |
||||||
|
daemon.stopDaemon(); |
||||||
|
} catch (Throwable tr) { |
||||||
|
Log.e(TAG, "", tr); |
||||||
|
} |
||||||
|
quit(); //TODO make menu items for starting i2pd. On my Android, I need to reboot the OS to restart i2pd.
|
||||||
|
}, "stop").start(); |
||||||
|
} |
||||||
|
|
||||||
|
private static volatile Timer gracefulQuitTimer; |
||||||
|
|
||||||
|
private void i2pdGracefulStop() { |
||||||
|
if (daemon.getState() == DaemonSingleton.State.stopped) { |
||||||
|
Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show(); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (getGracefulQuitTimer() != null) { |
||||||
|
Toast.makeText(this, R.string.graceful_stop_is_already_in_progress, Toast.LENGTH_SHORT).show(); |
||||||
|
return; |
||||||
|
} |
||||||
|
Toast.makeText(this, R.string.graceful_stop_is_in_progress, Toast.LENGTH_SHORT).show(); |
||||||
|
new Thread(() -> { |
||||||
|
try { |
||||||
|
Log.d(TAG, "graceful stopping"); |
||||||
|
if (daemon.isStartedOkay()) { |
||||||
|
daemon.stopAcceptingTunnels(); |
||||||
|
long gracefulStopAtMillis; |
||||||
|
synchronized (graceStartedMillis_LOCK) { |
||||||
|
graceStartedMillis = System.currentTimeMillis(); |
||||||
|
gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS; |
||||||
|
} |
||||||
|
rescheduleGraceStop(null, gracefulStopAtMillis); |
||||||
|
} else |
||||||
|
i2pdStop(); |
||||||
|
} catch(Throwable tr) { |
||||||
|
Log.e(TAG, "", tr); |
||||||
|
} |
||||||
|
}, "gracInit").start(); |
||||||
|
} |
||||||
|
|
||||||
|
private void cancelGracefulStop() |
||||||
|
{ |
||||||
|
cancelGracefulStop0(); |
||||||
|
new Thread(() -> { |
||||||
|
try { |
||||||
|
Log.d(TAG, "canceling graceful stop"); |
||||||
|
if (daemon.isStartedOkay()) { |
||||||
|
daemon.startAcceptingTunnels(); |
||||||
|
runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show()); |
||||||
|
} else |
||||||
|
i2pdStop(); |
||||||
|
} catch(Throwable tr) { |
||||||
|
Log.e(TAG, "", tr); |
||||||
|
} |
||||||
|
}, "gracCancel").start(); |
||||||
|
} |
||||||
|
|
||||||
|
private void rescheduleGraceStop(Timer gracefulQuitTimerOld, long gracefulStopAtMillis) { |
||||||
|
if (gracefulQuitTimerOld != null) |
||||||
|
gracefulQuitTimerOld.cancel(); |
||||||
|
|
||||||
|
if(daemon.GetTransitTunnelsCount() <= 0) { // no tunnels left
|
||||||
|
Log.d(TAG, "no transit tunnels left, stopping"); |
||||||
|
i2pdStop(); |
||||||
|
} |
||||||
|
|
||||||
|
final Timer gracefulQuitTimer = new Timer(true); |
||||||
|
setGracefulQuitTimer(gracefulQuitTimer); |
||||||
|
gracefulQuitTimer.schedule(new TimerTask() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
i2pdStop(); |
||||||
|
} |
||||||
|
|
||||||
|
}, Math.max(0, gracefulStopAtMillis - System.currentTimeMillis())); |
||||||
|
final TimerTask tickerTask = new TimerTask() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
daemonStateUpdatedListener.daemonStateUpdate(); |
||||||
|
} |
||||||
|
}; |
||||||
|
gracefulQuitTimer.scheduleAtFixedRate(tickerTask, 0/*start delay*/, 1000/*millis period*/); |
||||||
|
} |
||||||
|
|
||||||
|
private static Timer getGracefulQuitTimer() { |
||||||
|
return gracefulQuitTimer; |
||||||
|
} |
||||||
|
|
||||||
|
private void setGracefulQuitTimer(Timer gracefulQuitTimer) { |
||||||
|
I2PDActivity.gracefulQuitTimer = gracefulQuitTimer; |
||||||
|
runOnUiThread(()-> { |
||||||
|
Menu menu = optionsMenu; |
||||||
|
if (menu != null) { |
||||||
|
MenuItem item = menu.findItem(R.id.action_graceful_stop); |
||||||
|
if (item != null) { |
||||||
|
synchronized (graceStartedMillis_LOCK) { |
||||||
|
item.setTitle(getGracefulQuitTimer() != null ? R.string.action_cancel_graceful_stop : R.string.action_graceful_stop); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy the asset at the specified path to this app's data directory. If the |
||||||
|
* asset is a directory, its contents are also copied. |
||||||
|
* |
||||||
|
* @param path |
||||||
|
* Path to asset, relative to app's assets directory. |
||||||
|
*/ |
||||||
|
private void copyAsset(String path) { |
||||||
|
AssetManager manager = getAssets(); |
||||||
|
|
||||||
|
// If we have a directory, we make it and recurse. If a file, we copy its
|
||||||
|
// contents.
|
||||||
|
try { |
||||||
|
String[] contents = manager.list(path); |
||||||
|
|
||||||
|
// The documentation suggests that list throws an IOException, but doesn't
|
||||||
|
// say under what conditions. It'd be nice if it did so when the path was
|
||||||
|
// to a file. That doesn't appear to be the case. If the returned array is
|
||||||
|
// null or has 0 length, we assume the path is to a file. This means empty
|
||||||
|
// directories will get turned into files.
|
||||||
|
if (contents == null || contents.length == 0) { |
||||||
|
copyFileAsset(path); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Make the directory.
|
||||||
|
File dir = new File(i2pdpath, path); |
||||||
|
boolean result = dir.mkdirs(); |
||||||
|
Log.d(TAG, "dir.mkdirs() returned " + result); |
||||||
|
|
||||||
|
// Recurse on the contents.
|
||||||
|
for (String entry : contents) { |
||||||
|
copyAsset(path + '/' + entry); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG, "ex ignored for path='" + path + "'", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy the asset file specified by path to app's data directory. Assumes |
||||||
|
* parent directories have already been created. |
||||||
|
* |
||||||
|
* @param path |
||||||
|
* Path to asset, relative to app's assets directory. |
||||||
|
*/ |
||||||
|
private void copyFileAsset(String path) { |
||||||
|
File file = new File(i2pdpath, path); |
||||||
|
if (!file.exists()) { |
||||||
|
try { |
||||||
|
try (InputStream in = getAssets().open(path)) { |
||||||
|
try (OutputStream out = new FileOutputStream(file)) { |
||||||
|
byte[] buffer = new byte[1024]; |
||||||
|
int read = in.read(buffer); |
||||||
|
while (read != -1) { |
||||||
|
out.write(buffer, 0, read); |
||||||
|
read = in.read(buffer); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG, "", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void deleteRecursive(File fileOrDirectory) { |
||||||
|
if (fileOrDirectory.isDirectory()) { |
||||||
|
File[] files = fileOrDirectory.listFiles(); |
||||||
|
if (files != null) { |
||||||
|
for (File child : files) { |
||||||
|
deleteRecursive(child); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
boolean deleteResult = fileOrDirectory.delete(); |
||||||
|
if (!deleteResult) |
||||||
|
Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'"); |
||||||
|
} |
||||||
|
|
||||||
|
private void processAssets() { |
||||||
|
if (!assetsCopied) { |
||||||
|
try { |
||||||
|
assetsCopied = true; // prevent from running on every state update
|
||||||
|
|
||||||
|
File holderFile = new File(i2pdpath, "assets.ready"); |
||||||
|
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX
|
||||||
|
StringBuilder text = new StringBuilder(); |
||||||
|
|
||||||
|
if (holderFile.exists()) { |
||||||
|
try { // if holder file exists, read assets version string
|
||||||
|
FileReader fileReader = new FileReader(holderFile); |
||||||
|
|
||||||
|
try { |
||||||
|
BufferedReader br = new BufferedReader(fileReader); |
||||||
|
|
||||||
|
try { |
||||||
|
String line; |
||||||
|
|
||||||
|
while ((line = br.readLine()) != null) { |
||||||
|
text.append(line); |
||||||
|
} |
||||||
|
}finally { |
||||||
|
try { |
||||||
|
br.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG, "", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
fileReader.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG, "", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG, "", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if version differs from current app version or null, try to delete certificates folder
|
||||||
|
if (!text.toString().contains(versionName)) |
||||||
|
try { |
||||||
|
boolean deleteResult = holderFile.delete(); |
||||||
|
if (!deleteResult) |
||||||
|
Log.e(TAG, "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile.getAbsolutePath() + "'"); |
||||||
|
File certPath = new File(i2pdpath, "certificates"); |
||||||
|
deleteRecursive(certPath); |
||||||
|
} |
||||||
|
catch (Throwable tr) { |
||||||
|
Log.e(TAG, "", tr); |
||||||
|
} |
||||||
|
|
||||||
|
// copy assets. If processed file exists, it won't be overwritten
|
||||||
|
copyAsset("addressbook"); |
||||||
|
copyAsset("certificates"); |
||||||
|
copyAsset("tunnels.d"); |
||||||
|
copyAsset("i2pd.conf"); |
||||||
|
copyAsset("subscriptions.txt"); |
||||||
|
copyAsset("tunnels.conf"); |
||||||
|
|
||||||
|
// update holder file about successful copying
|
||||||
|
FileWriter writer = new FileWriter(holderFile); |
||||||
|
try { |
||||||
|
writer.append(versionName); |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
writer.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG,"on writer close", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
catch (Throwable tr) |
||||||
|
{ |
||||||
|
Log.e(TAG,"on assets copying", tr); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressLint("BatteryLife") |
||||||
|
private void openBatteryOptimizationDialogIfNeeded() { |
||||||
|
boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true); |
||||||
|
Log.i(TAG, "BATT_OPTIM_questionEnabled==" + questionEnabled); |
||||||
|
if (!isKnownIgnoringBatteryOptimizations() |
||||||
|
&& android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M |
||||||
|
&& questionEnabled) { |
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this); |
||||||
|
builder.setTitle(R.string.battery_optimizations_enabled); |
||||||
|
builder.setMessage(R.string.battery_optimizations_enabled_dialog); |
||||||
|
builder.setPositiveButton(R.string.continue_str, (dialog, which) -> { |
||||||
|
try { |
||||||
|
startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse(PACKAGE_URI_SCHEME + getPackageName()))); |
||||||
|
} catch (ActivityNotFoundException e) { |
||||||
|
Log.e(TAG, "BATT_OPTIM_ActvtNotFound", e); |
||||||
|
Toast.makeText(this, R.string.device_does_not_support_disabling_battery_optimizations, Toast.LENGTH_SHORT).show(); |
||||||
|
} |
||||||
|
}); |
||||||
|
builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain()); |
||||||
|
final AlertDialog dialog = builder.create(); |
||||||
|
dialog.setCanceledOnTouchOutside(false); |
||||||
|
dialog.show(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void setNeverAskForBatteryOptimizationsAgain() { |
||||||
|
getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply(); |
||||||
|
} |
||||||
|
|
||||||
|
protected boolean isKnownIgnoringBatteryOptimizations() { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||||
|
final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); |
||||||
|
if (pm == null) { |
||||||
|
Log.i(TAG, "BATT_OPTIM: POWER_SERVICE==null"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
boolean ignoring = pm.isIgnoringBatteryOptimizations(getPackageName()); |
||||||
|
Log.i(TAG, "BATT_OPTIM: ignoring==" + ignoring); |
||||||
|
return ignoring; |
||||||
|
} else { |
||||||
|
Log.i(TAG, "BATT_OPTIM: old sdk version==" + Build.VERSION.SDK_INT); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected SharedPreferences getPreferences() { |
||||||
|
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); |
||||||
|
} |
||||||
|
|
||||||
|
private String getBatteryOptimizationPreferenceKey() { |
||||||
|
@SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); |
||||||
|
return "show_battery_optimization" + (device == null ? "" : device); |
||||||
|
} |
||||||
|
|
||||||
|
private void quit() { |
||||||
|
try { |
||||||
|
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); |
||||||
|
} |
||||||
|
try { |
||||||
|
daemon.stopDaemon(); |
||||||
|
} catch (Throwable tr) { |
||||||
|
Log.e(TAG, "", tr); |
||||||
|
} |
||||||
|
System.exit(0); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,170 @@ |
|||||||
|
package org.purplei2p.i2pd; |
||||||
|
|
||||||
|
import android.Manifest; |
||||||
|
import android.app.Activity; |
||||||
|
import android.content.Intent; |
||||||
|
import android.content.pm.PackageManager; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.Button; |
||||||
|
import android.widget.TextView; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
//dangerous perms, per https://developer.android.com/guide/topics/permissions/normal-permissions.html :
|
||||||
|
//android.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
public class I2PDPermsAskerActivity extends Activity { |
||||||
|
|
||||||
|
private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0; |
||||||
|
|
||||||
|
private Button button_request_write_ext_storage_perms; |
||||||
|
private TextView textview_retry; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
//if less than Android 6, no runtime perms req system present
|
||||||
|
if (android.os.Build.VERSION.SDK_INT < 23) { |
||||||
|
startMainActivity(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_perms_asker); |
||||||
|
button_request_write_ext_storage_perms = (Button) findViewById(R.id.button_request_write_ext_storage_perms); |
||||||
|
textview_retry = (TextView) findViewById(R.id.textview_retry); |
||||||
|
|
||||||
|
button_request_write_ext_storage_perms.setOnClickListener(new View.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(View view) { |
||||||
|
request_write_ext_storage_perms(); |
||||||
|
} |
||||||
|
}); |
||||||
|
request_write_ext_storage_perms(); |
||||||
|
} |
||||||
|
|
||||||
|
private void request_write_ext_storage_perms() { |
||||||
|
|
||||||
|
textview_retry.setVisibility(TextView.GONE); |
||||||
|
button_request_write_ext_storage_perms.setVisibility(Button.GONE); |
||||||
|
|
||||||
|
Method methodCheckPermission; |
||||||
|
Method method_shouldShowRequestPermissionRationale; |
||||||
|
Method method_requestPermissions; |
||||||
|
try { |
||||||
|
methodCheckPermission = getClass().getMethod("checkSelfPermission", String.class); |
||||||
|
method_shouldShowRequestPermissionRationale = |
||||||
|
getClass().getMethod("shouldShowRequestPermissionRationale", String.class); |
||||||
|
method_requestPermissions = |
||||||
|
getClass().getMethod("requestPermissions", String[].class, int.class); |
||||||
|
} catch (NoSuchMethodException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
Integer resultObj; |
||||||
|
try { |
||||||
|
resultObj = (Integer) methodCheckPermission.invoke( |
||||||
|
this, Manifest.permission.WRITE_EXTERNAL_STORAGE); |
||||||
|
} catch (Throwable e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
|
||||||
|
if (resultObj != PackageManager.PERMISSION_GRANTED) { |
||||||
|
|
||||||
|
// Should we show an explanation?
|
||||||
|
Boolean aBoolean; |
||||||
|
try { |
||||||
|
aBoolean = (Boolean) method_shouldShowRequestPermissionRationale.invoke(this, |
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
if (aBoolean) { |
||||||
|
|
||||||
|
// Show an explanation to the user *asynchronously* -- don't block
|
||||||
|
// this thread waiting for the user's response! After the user
|
||||||
|
// sees the explanation, try again to request the permission.
|
||||||
|
|
||||||
|
showExplanation(); |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
// No explanation needed, we can request the permission.
|
||||||
|
|
||||||
|
try { |
||||||
|
method_requestPermissions.invoke(this, |
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, |
||||||
|
PERMISSION_WRITE_EXTERNAL_STORAGE); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} else startMainActivity(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRequestPermissionsResult(int requestCode, |
||||||
|
String permissions[], int[] grantResults) { |
||||||
|
switch (requestCode) { |
||||||
|
case PERMISSION_WRITE_EXTERNAL_STORAGE: { |
||||||
|
// If request is cancelled, the result arrays are empty.
|
||||||
|
if (grantResults.length > 0 |
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
||||||
|
|
||||||
|
// permission was granted, yay! Do the
|
||||||
|
// contacts-related task you need to do.
|
||||||
|
|
||||||
|
startMainActivity(); |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
// permission denied, boo! Disable the
|
||||||
|
// functionality that depends on this permission.
|
||||||
|
textview_retry.setText(R.string.permDenied); |
||||||
|
textview_retry.setVisibility(TextView.VISIBLE); |
||||||
|
button_request_write_ext_storage_perms.setVisibility(Button.VISIBLE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// other 'case' lines to check for other
|
||||||
|
// permissions this app might request.
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void startMainActivity() { |
||||||
|
startActivity(new Intent(this, I2PDActivity.class)); |
||||||
|
finish(); |
||||||
|
} |
||||||
|
|
||||||
|
private static final int SHOW_EXPLANATION_REQUEST = 1; // The request code
|
||||||
|
private void showExplanation() { |
||||||
|
Intent intent = new Intent(this, I2PDPermsExplanationActivity.class); |
||||||
|
startActivityForResult(intent, SHOW_EXPLANATION_REQUEST); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
||||||
|
// Check which request we're responding to
|
||||||
|
if (requestCode == SHOW_EXPLANATION_REQUEST) { |
||||||
|
// Make sure the request was successful
|
||||||
|
if (resultCode == RESULT_OK) { |
||||||
|
// Request the permission
|
||||||
|
Method method_requestPermissions; |
||||||
|
try { |
||||||
|
method_requestPermissions = |
||||||
|
getClass().getMethod("requestPermissions", String[].class, int.class); |
||||||
|
} catch (NoSuchMethodException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
try { |
||||||
|
method_requestPermissions.invoke(this, |
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, |
||||||
|
PERMISSION_WRITE_EXTERNAL_STORAGE); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} else { |
||||||
|
finish(); //close the app
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package org.purplei2p.i2pd; |
||||||
|
|
||||||
|
import android.app.ActionBar; |
||||||
|
import android.content.Intent; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.app.Activity; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.Button; |
||||||
|
|
||||||
|
public class I2PDPermsExplanationActivity extends Activity { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
setContentView(R.layout.activity_perms_explanation); |
||||||
|
ActionBar actionBar = getActionBar(); |
||||||
|
if(actionBar!=null)actionBar.setHomeButtonEnabled(false); |
||||||
|
Button button_ok = (Button) findViewById(R.id.button_ok); |
||||||
|
button_ok.setOnClickListener(new View.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(View view) { |
||||||
|
returnFromActivity(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void returnFromActivity() { |
||||||
|
Intent data = new Intent(); |
||||||
|
Activity parent = getParent(); |
||||||
|
if (parent == null) { |
||||||
|
setResult(Activity.RESULT_OK, data); |
||||||
|
} else { |
||||||
|
parent.setResult(Activity.RESULT_OK, data); |
||||||
|
} |
||||||
|
finish(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
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 startAcceptingTunnels(); |
||||||
|
|
||||||
|
public static native void reloadTunnelsConfigs(); |
||||||
|
|
||||||
|
public static native void onNetworkStateChanged(boolean isConnected); |
||||||
|
|
||||||
|
public static native void setDataDir(String jdataDir); |
||||||
|
|
||||||
|
public static native int GetTransitTunnelsCount(); |
||||||
|
|
||||||
|
public static void loadLibraries() { |
||||||
|
//System.loadLibrary("c++_shared");
|
||||||
|
System.loadLibrary("i2pd"); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,26 @@ |
|||||||
|
<LinearLayout android:id="@+id/main_layout" |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical" |
||||||
|
android:paddingBottom="@dimen/vertical_page_margin" |
||||||
|
android:paddingLeft="@dimen/horizontal_page_margin" |
||||||
|
android:paddingRight="@dimen/horizontal_page_margin" |
||||||
|
android:paddingTop="@dimen/vertical_page_margin" |
||||||
|
tools:context=".I2PDPermsAskerActivity"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/textview_retry" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginBottom="@dimen/horizontal_page_margin" |
||||||
|
android:visibility="gone" /> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/button_request_write_ext_storage_perms" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:text="@string/retryPermRequest" |
||||||
|
android:visibility="gone" /> |
||||||
|
</LinearLayout> |
@ -0,0 +1,25 @@ |
|||||||
|
<LinearLayout android:id="@+id/layout_prompt" |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="vertical" |
||||||
|
android:paddingBottom="@dimen/vertical_page_margin" |
||||||
|
android:paddingLeft="@dimen/horizontal_page_margin" |
||||||
|
android:paddingRight="@dimen/horizontal_page_margin" |
||||||
|
android:paddingTop="@dimen/vertical_page_margin" |
||||||
|
tools:context=".I2PDPermsAskerActivity"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/textview_explanation" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginBottom="@dimen/horizontal_page_margin" |
||||||
|
android:text="@string/permRequired" /> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/button_ok" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:text="@string/ok" /> |
||||||
|
</LinearLayout> |
@ -0,0 +1,12 @@ |
|||||||
|
<LinearLayout android:id="@+id/layout_prompt" |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:layout_width="fill_parent" |
||||||
|
android:layout_height="fill_parent" |
||||||
|
tools:context=".I2PDActivity"> |
||||||
|
|
||||||
|
<WebView |
||||||
|
android:id="@+id/webview1" |
||||||
|
android:layout_width="fill_parent" |
||||||
|
android:layout_height="fill_parent" /> |
||||||
|
</LinearLayout> |
@ -0,0 +1,31 @@ |
|||||||
|
<menu |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
tools:context=".I2PDActivity"> |
||||||
|
|
||||||
|
<group android:id="@+id/group_various"> |
||||||
|
<item |
||||||
|
android:id="@+id/action_battery_otimizations" |
||||||
|
android:title="@string/menu_item_battery_optimizations_str" /> |
||||||
|
</group> |
||||||
|
|
||||||
|
<group android:id="@+id/group_i2pd_control"> |
||||||
|
<item |
||||||
|
android:id="@+id/action_start_webview" |
||||||
|
android:orderInCategory="96" |
||||||
|
android:title="@string/action_start_webview" /> |
||||||
|
<item |
||||||
|
android:id="@+id/action_reload_tunnels_config" |
||||||
|
android:orderInCategory="97" |
||||||
|
android:title="@string/action_reload_tunnels_config" /> |
||||||
|
<item |
||||||
|
android:id="@+id/action_graceful_stop" |
||||||
|
android:orderInCategory="98" |
||||||
|
android:title="@string/action_graceful_stop" /> |
||||||
|
<item |
||||||
|
android:id="@+id/action_stop" |
||||||
|
android:orderInCategory="99" |
||||||
|
android:title="@string/action_stop" /> |
||||||
|
</group> |
||||||
|
</menu> |
@ -0,0 +1,40 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<resources> |
||||||
|
<string name="app_name">i2pd</string> |
||||||
|
|
||||||
|
<string name="action_stop">Остановить</string> |
||||||
|
<string name="action_graceful_stop">Корректная остановка</string> |
||||||
|
<string name="action_cancel_graceful_stop">Отменить корректную остановку</string> |
||||||
|
<string name="action_reload_tunnels_config">Перезагрузить туннели</string> |
||||||
|
<string name="action_start_webview">Открыть Веб Консоль</string> |
||||||
|
|
||||||
|
<string name="graceful_stop_is_already_in_progress">Корректная остановка уже запущена</string> |
||||||
|
<string name="graceful_stop_is_in_progress">Корректная остановка запущена</string> |
||||||
|
<string name="gracefulShutdownInProgress">Корректная остановка запущена</string> |
||||||
|
|
||||||
|
<string name="already_stopped">Уже остановлено</string> |
||||||
|
<string name="uninitialized">Приложение инициализируется</string> |
||||||
|
<string name="starting">Приложение запускается</string> |
||||||
|
<string name="jniLibraryLoaded">Загружены JNI библиотеки</string> |
||||||
|
<string name="startedOkay">Приложение запущено</string> |
||||||
|
<string name="startFailed">Запуск не удался</string> |
||||||
|
<string name="stopped">Приложение было остановлено</string> |
||||||
|
<string name="remaining">осталось</string> |
||||||
|
|
||||||
|
<string name="title_activity_i2_pdperms_asker_prompt">Запрос</string> |
||||||
|
<string name="permDenied">Права для записи на SD карту отклонены, вам необходимо предоставить их для продолжения</string> |
||||||
|
<string name="permRequired">Права на запись на SD карту необходимы для записи ключей и других файлов в папку I2PD на внутренней памяти.</string> |
||||||
|
<string name="retryPermRequest">Повторить запрос прав на запись на SD карту</string> |
||||||
|
|
||||||
|
<string name="menu_item_battery_optimizations_str">Оптимизации аккумулятора</string> |
||||||
|
<string name="battery_optimizations_enabled">Оптимизации аккумулятора включены</string> |
||||||
|
<string name="battery_optimizations_enabled_explained">Ваша операционная система осуществляет оптимизации расхода аккумулятора, которые могут приводить к выгрузке I2PD из памяти и прекращению его работы с целью сэкономить заряд аккумулятора.\nРекомендуется отключить эти оптимизации.</string> |
||||||
|
<string name="battery_optimizations_enabled_dialog">Ваша операционная система осуществляет оптимизации расхода аккумулятора, которые могут приводить к выгрузке I2PD из памяти и прекращению его работы с целью сэкономить заряд аккумулятора.\n\nВам сейчас будет предложено разрешить отключение этих оптимизаций.</string> |
||||||
|
<string name="continue_str">Продолжить</string> |
||||||
|
<string name="device_does_not_support_disabling_battery_optimizations">Ваша версия Андроид не поддерживает отключение оптимизаций аккумулятора</string> |
||||||
|
<string name="os_version_does_not_support_battery_optimizations_show_os_dialog_api">Ваша версия Андроид не поддерживает показ диалога об оптимизациях аккумулятора для приложений.</string> |
||||||
|
|
||||||
|
<string name="shutdown_canceled">Плановая остановка отменена</string> |
||||||
|
|
||||||
|
<string name="tunnels_reloading">Перезагрузка конфигурации туннелей...</string> |
||||||
|
</resources> |
@ -0,0 +1,41 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> |
||||||
|
<string name="app_name">i2pd</string> |
||||||
|
|
||||||
|
<string name="action_stop">Stop</string> |
||||||
|
<string name="action_graceful_stop">Graceful Stop</string> |
||||||
|
<string name="action_cancel_graceful_stop">Cancel Graceful Stop</string> |
||||||
|
<string name="action_reload_tunnels_config">Reload tunnels</string> |
||||||
|
<string name="action_start_webview">Open Web Console</string> |
||||||
|
|
||||||
|
<string name="graceful_stop_is_already_in_progress">Graceful stop is already in progress</string> |
||||||
|
<string name="graceful_stop_is_in_progress">Graceful stop is in progress</string> |
||||||
|
<string name="gracefulShutdownInProgress">Graceful shutdown in progress</string> |
||||||
|
|
||||||
|
<string name="already_stopped">Already stopped</string> |
||||||
|
<string name="uninitialized">Application initializing</string> |
||||||
|
<string name="starting">Application starting</string> |
||||||
|
<string name="jniLibraryLoaded">Loaded JNI libraries</string> |
||||||
|
<string name="startedOkay">Application Started</string> |
||||||
|
<string name="startFailed">Start failed</string> |
||||||
|
<string name="stopped">Application stopped</string> |
||||||
|
<string name="remaining">remaining</string> |
||||||
|
<string name="ok">OK</string> |
||||||
|
|
||||||
|
<string name="title_activity_i2_pdperms_asker_prompt">Prompt</string> |
||||||
|
<string name="permDenied">SD card write permission denied, you need to allow this to continue</string> |
||||||
|
<string name="permRequired">SD card write access is required to write the keys and other files to the I2PD folder on SD card.</string> |
||||||
|
<string name="retryPermRequest">Retry requesting the SD card write permissions</string> |
||||||
|
|
||||||
|
<string name="menu_item_battery_optimizations_str">Battery Optimizations</string> |
||||||
|
<string name="battery_optimizations_enabled">Battery optimizations enabled</string> |
||||||
|
<string name="battery_optimizations_enabled_explained">Your Android is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\nIt is recommended to allow disabling those battery optimizations.</string> |
||||||
|
<string name="battery_optimizations_enabled_dialog">Your Android is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\n\nYou will now be asked to allow to disable those.</string> |
||||||
|
<string name="continue_str">Continue</string> |
||||||
|
<string name="device_does_not_support_disabling_battery_optimizations">Your Android version does not support opting out of battery optimizations</string> |
||||||
|
<string name="os_version_does_not_support_battery_optimizations_show_os_dialog_api">Your Android OS version does not support showing the dialog for battery optimizations for applications.</string> |
||||||
|
|
||||||
|
<string name="shutdown_canceled">Planned shutdown canceled</string> |
||||||
|
|
||||||
|
<string name="tunnels_reloading">Reloading tunnels config...</string> |
||||||
|
</resources> |
@ -0,0 +1,16 @@ |
|||||||
|
<resources> |
||||||
|
|
||||||
|
<!-- Define standard dimensions to comply with Holo-style grids and rhythm. --> |
||||||
|
|
||||||
|
<dimen name="margin_tiny">4dp</dimen> |
||||||
|
<dimen name="margin_small">8dp</dimen> |
||||||
|
<dimen name="margin_medium">16dp</dimen> |
||||||
|
<dimen name="margin_large">32dp</dimen> |
||||||
|
<dimen name="margin_huge">64dp</dimen> |
||||||
|
|
||||||
|
<!-- Semantic definitions --> |
||||||
|
|
||||||
|
<dimen name="horizontal_page_margin">@dimen/margin_medium</dimen> |
||||||
|
<dimen name="vertical_page_margin">@dimen/margin_medium</dimen> |
||||||
|
|
||||||
|
</resources> |
@ -0,0 +1,18 @@ |
|||||||
|
buildscript { |
||||||
|
repositories { |
||||||
|
mavenCentral() |
||||||
|
jcenter() |
||||||
|
google() |
||||||
|
} |
||||||
|
dependencies { |
||||||
|
classpath 'com.android.tools.build:gradle:3.4.2' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
allprojects { |
||||||
|
repositories { |
||||||
|
mavenCentral() |
||||||
|
google() |
||||||
|
jcenter() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project name="i2pd" default="help"> |
||||||
|
|
||||||
|
<!-- The local.properties file is created and updated by the 'android' tool. |
||||||
|
It contains the path to the SDK. It should *NOT* be checked into |
||||||
|
Version Control Systems. --> |
||||||
|
<property file="local.properties" /> |
||||||
|
|
||||||
|
<!-- The ant.properties file can be created by you. It is only edited by the |
||||||
|
'android' tool to add properties to it. |
||||||
|
This is the place to change some Ant specific build properties. |
||||||
|
Here are some properties you may want to change/update: |
||||||
|
|
||||||
|
source.dir |
||||||
|
The name of the source directory. Default is 'src'. |
||||||
|
out.dir |
||||||
|
The name of the output directory. Default is 'bin'. |
||||||
|
|
||||||
|
For other overridable properties, look at the beginning of the rules |
||||||
|
files in the SDK, at tools/ant/build.xml |
||||||
|
|
||||||
|
Properties related to the SDK location or the project target should |
||||||
|
be updated using the 'android' tool with the 'update' action. |
||||||
|
|
||||||
|
This file is an integral part of the build system for your |
||||||
|
application and should be checked into Version Control Systems. |
||||||
|
|
||||||
|
--> |
||||||
|
<property file="ant.properties" /> |
||||||
|
|
||||||
|
<!-- if sdk.dir was not set from one of the property file, then |
||||||
|
get it from the ANDROID_HOME env var. |
||||||
|
This must be done before we load project.properties since |
||||||
|
the proguard config can use sdk.dir --> |
||||||
|
<property environment="env" /> |
||||||
|
<condition property="sdk.dir" value="${env.ANDROID_HOME}"> |
||||||
|
<isset property="env.ANDROID_HOME" /> |
||||||
|
</condition> |
||||||
|
|
||||||
|
<!-- The project.properties file is created and updated by the 'android' |
||||||
|
tool, as well as ADT. |
||||||
|
|
||||||
|
This contains project specific properties such as project target, and library |
||||||
|
dependencies. Lower level build properties are stored in ant.properties |
||||||
|
(or in .classpath for Eclipse projects). |
||||||
|
|
||||||
|
This file is an integral part of the build system for your |
||||||
|
application and should be checked into Version Control Systems. --> |
||||||
|
<loadproperties srcFile="project.properties" /> |
||||||
|
|
||||||
|
<!-- quick check on sdk.dir --> |
||||||
|
<fail |
||||||
|
message="sdk.dir is missing. Insert sdk.dir=... into './local.properties'. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." |
||||||
|
unless="sdk.dir" |
||||||
|
/> |
||||||
|
|
||||||
|
<fail |
||||||
|
message="ndk.dir is missing. Insert ndk.dir=... into './local.properties'." |
||||||
|
unless="ndk.dir" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- |
||||||
|
Import per project custom build rules if present at the root of the project. |
||||||
|
This is the place to put custom intermediary targets such as: |
||||||
|
-pre-build |
||||||
|
-pre-compile |
||||||
|
-post-compile (This is typically used for code obfuscation. |
||||||
|
Compiled code location: ${out.classes.absolute.dir} |
||||||
|
If this is not done in place, override ${out.dex.input.absolute.dir}) |
||||||
|
-post-package |
||||||
|
-post-build |
||||||
|
-pre-clean |
||||||
|
--> |
||||||
|
<import file="custom_rules.xml" optional="true" /> |
||||||
|
|
||||||
|
<!-- Import the actual build file. |
||||||
|
|
||||||
|
To customize existing targets, there are two options: |
||||||
|
- Customize only one target: |
||||||
|
- copy/paste the target into this file, *before* the |
||||||
|
<import> task. |
||||||
|
- customize it to your needs. |
||||||
|
- Customize the whole content of build.xml |
||||||
|
- copy/paste the content of the rules files (minus the top node) |
||||||
|
into this file, replacing the <import> task. |
||||||
|
- customize to your needs. |
||||||
|
|
||||||
|
*********************** |
||||||
|
****** IMPORTANT ****** |
||||||
|
*********************** |
||||||
|
In all cases you must update the value of version-tag below to read 'custom' instead of an integer, |
||||||
|
in order to avoid having your file be overridden by tools such as "android update project" |
||||||
|
--> |
||||||
|
<!-- version-tag: 1 --> |
||||||
|
<import file="${sdk.dir}/tools/ant/build.xml" /> |
||||||
|
</project> |
@ -0,0 +1,3 @@ |
|||||||
|
android.enableJetifier=true |
||||||
|
android.useAndroidX=true |
||||||
|
org.gradle.parallel=true |
Binary file not shown.
@ -0,0 +1,6 @@ |
|||||||
|
#Tue Aug 20 14:39:08 MSK 2019 |
||||||
|
distributionBase=GRADLE_USER_HOME |
||||||
|
distributionPath=wrapper/dists |
||||||
|
zipStoreBase=GRADLE_USER_HOME |
||||||
|
zipStorePath=wrapper/dists |
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip |
@ -0,0 +1,172 @@ |
|||||||
|
#!/usr/bin/env sh |
||||||
|
|
||||||
|
############################################################################## |
||||||
|
## |
||||||
|
## Gradle start up script for UN*X |
||||||
|
## |
||||||
|
############################################################################## |
||||||
|
|
||||||
|
# Attempt to set APP_HOME |
||||||
|
# Resolve links: $0 may be a link |
||||||
|
PRG="$0" |
||||||
|
# Need this for relative symlinks. |
||||||
|
while [ -h "$PRG" ] ; do |
||||||
|
ls=`ls -ld "$PRG"` |
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||||
|
if expr "$link" : '/.*' > /dev/null; then |
||||||
|
PRG="$link" |
||||||
|
else |
||||||
|
PRG=`dirname "$PRG"`"/$link" |
||||||
|
fi |
||||||
|
done |
||||||
|
SAVED="`pwd`" |
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null |
||||||
|
APP_HOME="`pwd -P`" |
||||||
|
cd "$SAVED" >/dev/null |
||||||
|
|
||||||
|
APP_NAME="Gradle" |
||||||
|
APP_BASE_NAME=`basename "$0"` |
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||||
|
DEFAULT_JVM_OPTS="" |
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value. |
||||||
|
MAX_FD="maximum" |
||||||
|
|
||||||
|
warn () { |
||||||
|
echo "$*" |
||||||
|
} |
||||||
|
|
||||||
|
die () { |
||||||
|
echo |
||||||
|
echo "$*" |
||||||
|
echo |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false'). |
||||||
|
cygwin=false |
||||||
|
msys=false |
||||||
|
darwin=false |
||||||
|
nonstop=false |
||||||
|
case "`uname`" in |
||||||
|
CYGWIN* ) |
||||||
|
cygwin=true |
||||||
|
;; |
||||||
|
Darwin* ) |
||||||
|
darwin=true |
||||||
|
;; |
||||||
|
MINGW* ) |
||||||
|
msys=true |
||||||
|
;; |
||||||
|
NONSTOP* ) |
||||||
|
nonstop=true |
||||||
|
;; |
||||||
|
esac |
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM. |
||||||
|
if [ -n "$JAVA_HOME" ] ; then |
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||||
|
# IBM's JDK on AIX uses strange locations for the executables |
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||||
|
else |
||||||
|
JAVACMD="$JAVA_HOME/bin/java" |
||||||
|
fi |
||||||
|
if [ ! -x "$JAVACMD" ] ; then |
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the |
||||||
|
location of your Java installation." |
||||||
|
fi |
||||||
|
else |
||||||
|
JAVACMD="java" |
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the |
||||||
|
location of your Java installation." |
||||||
|
fi |
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can. |
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
||||||
|
MAX_FD_LIMIT=`ulimit -H -n` |
||||||
|
if [ $? -eq 0 ] ; then |
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
||||||
|
MAX_FD="$MAX_FD_LIMIT" |
||||||
|
fi |
||||||
|
ulimit -n $MAX_FD |
||||||
|
if [ $? -ne 0 ] ; then |
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD" |
||||||
|
fi |
||||||
|
else |
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
||||||
|
fi |
||||||
|
fi |
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock |
||||||
|
if $darwin; then |
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
||||||
|
fi |
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java |
||||||
|
if $cygwin ; then |
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"` |
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath |
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
||||||
|
SEP="" |
||||||
|
for dir in $ROOTDIRSRAW ; do |
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir" |
||||||
|
SEP="|" |
||||||
|
done |
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))" |
||||||
|
# Add a user-defined pattern to the cygpath arguments |
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
||||||
|
fi |
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
||||||
|
i=0 |
||||||
|
for arg in "$@" ; do |
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
||||||
|
else |
||||||
|
eval `echo args$i`="\"$arg\"" |
||||||
|
fi |
||||||
|
i=$((i+1)) |
||||||
|
done |
||||||
|
case $i in |
||||||
|
(0) set -- ;; |
||||||
|
(1) set -- "$args0" ;; |
||||||
|
(2) set -- "$args0" "$args1" ;; |
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;; |
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
||||||
|
esac |
||||||
|
fi |
||||||
|
|
||||||
|
# Escape application args |
||||||
|
save () { |
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
||||||
|
echo " " |
||||||
|
} |
||||||
|
APP_ARGS=$(save "$@") |
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules |
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong |
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then |
||||||
|
cd "$(dirname "$0")" |
||||||
|
fi |
||||||
|
|
||||||
|
exec "$JAVACMD" "$@" |
@ -0,0 +1,84 @@ |
|||||||
|
@if "%DEBUG%" == "" @echo off |
||||||
|
@rem ########################################################################## |
||||||
|
@rem |
||||||
|
@rem Gradle startup script for Windows |
||||||
|
@rem |
||||||
|
@rem ########################################################################## |
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell |
||||||
|
if "%OS%"=="Windows_NT" setlocal |
||||||
|
|
||||||
|
set DIRNAME=%~dp0 |
||||||
|
if "%DIRNAME%" == "" set DIRNAME=. |
||||||
|
set APP_BASE_NAME=%~n0 |
||||||
|
set APP_HOME=%DIRNAME% |
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||||
|
set DEFAULT_JVM_OPTS= |
||||||
|
|
||||||
|
@rem Find java.exe |
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome |
||||||
|
|
||||||
|
set JAVA_EXE=java.exe |
||||||
|
%JAVA_EXE% -version >NUL 2>&1 |
||||||
|
if "%ERRORLEVEL%" == "0" goto init |
||||||
|
|
||||||
|
echo. |
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||||
|
echo. |
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the |
||||||
|
echo location of your Java installation. |
||||||
|
|
||||||
|
goto fail |
||||||
|
|
||||||
|
:findJavaFromJavaHome |
||||||
|
set JAVA_HOME=%JAVA_HOME:"=% |
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init |
||||||
|
|
||||||
|
echo. |
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
||||||
|
echo. |
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the |
||||||
|
echo location of your Java installation. |
||||||
|
|
||||||
|
goto fail |
||||||
|
|
||||||
|
:init |
||||||
|
@rem Get command-line arguments, handling Windows variants |
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args |
||||||
|
|
||||||
|
:win9xME_args |
||||||
|
@rem Slurp the command line arguments. |
||||||
|
set CMD_LINE_ARGS= |
||||||
|
set _SKIP=2 |
||||||
|
|
||||||
|
:win9xME_args_slurp |
||||||
|
if "x%~1" == "x" goto execute |
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%* |
||||||
|
|
||||||
|
:execute |
||||||
|
@rem Setup the command line |
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
||||||
|
|
||||||
|
@rem Execute Gradle |
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% |
||||||
|
|
||||||
|
:end |
||||||
|
@rem End local scope for the variables with windows NT shell |
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd |
||||||
|
|
||||||
|
:fail |
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
||||||
|
rem the _cmd.exe /c_ return code! |
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
||||||
|
exit /b 1 |
||||||
|
|
||||||
|
:mainEnd |
||||||
|
if "%OS%"=="Windows_NT" endlocal |
||||||
|
|
||||||
|
:omega |
Loading…
Reference in new issue