R4SAS
4 years ago
commit
e41ddc65cd
42 changed files with 3431 additions and 0 deletions
@ -0,0 +1,18 @@
@@ -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 @@
@@ -0,0 +1,4 @@
|
||||
[submodule "i2pd"] |
||||
path = app/jni/i2pd |
||||
url = https://github.com/PurpleI2P/i2pd.git |
||||
branch = openssl |
@ -0,0 +1,19 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1 @@
|
||||
Subproject commit 6735b2686b6c13a36546dd794ee49b4d583565e0 |
@ -0,0 +1,114 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1 @@
|
||||
../../contrib/certificates |
@ -0,0 +1,92 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1 @@
|
||||
../../contrib/tunnels.d |
@ -0,0 +1,181 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,3 @@
|
||||
android.enableJetifier=true |
||||
android.useAndroidX=true |
||||
org.gradle.parallel=true |
Binary file not shown.
@ -0,0 +1,6 @@
@@ -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 @@
@@ -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 @@
@@ -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