From 988940f50a110567e9ed6ae17d500439b8b845aa Mon Sep 17 00:00:00 2001
From: nonlin-lin-chaos-order-etc-etal
<19966907+nonlin-lin-chaos-order-etc-etal@users.noreply.github.com>
Date: Mon, 22 Jul 2024 01:44:19 +0800
Subject: [PATCH] Try to fix #50 (#76)
* Fix foreground notification
* Trying to fix app quit
* Remove unsupported UI items
* Remove misleading exceptions
* Set Unix exec bit on build scripts
* Revamp processAssets point to be launched
* Corrected logic of daemon status events
* Fix double I2PActivity.onCreate call hangup
* Fix exception processing @ processAssets
---
README.md | 7 +-
app/build.gradle | 10 +-
app/src/main/AndroidManifest.xml | 1 +
.../org/purplei2p/i2pd/AbstractProcess.java | 7 +
.../org/purplei2p/i2pd/DaemonWrapper.java | 172 +++++++++---------
.../org/purplei2p/i2pd/ForegroundService.java | 9 +
.../java/org/purplei2p/i2pd/I2PDActivity.java | 68 ++++---
.../java/org/purplei2p/i2pd/I2PD_JNI.java | 35 ----
.../main/java/org/purplei2p/i2pd/I2pdApi.java | 154 ++++++++++++++++
.../i2pd/NetworkStateChangeReceiver.java | 2 +-
.../purplei2p/i2pd/WebConsoleActivity.java | 2 +-
.../java/org/purplei2p/i2pd/appscope/App.java | 126 +++++++++++++
app/src/main/res/layout/activity_main.xml | 4 +-
app/src/main/res/menu/options_main.xml | 4 +-
binary/jni/build_debug.sh | 23 +++
binary/jni/build_release.sh | 20 ++
binary/jni/ndkbuild-wrapper.sh | 16 ++
gradle.properties | 1 +
18 files changed, 505 insertions(+), 156 deletions(-)
create mode 100644 app/src/main/java/org/purplei2p/i2pd/AbstractProcess.java
delete mode 100644 app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java
create mode 100644 app/src/main/java/org/purplei2p/i2pd/I2pdApi.java
create mode 100644 app/src/main/java/org/purplei2p/i2pd/appscope/App.java
create mode 100755 binary/jni/build_debug.sh
create mode 100755 binary/jni/build_release.sh
create mode 100755 binary/jni/ndkbuild-wrapper.sh
diff --git a/README.md b/README.md
index 1121a90..ccc84f1 100644
--- a/README.md
+++ b/README.md
@@ -53,10 +53,9 @@ export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export ANDROID_HOME=/opt/android-sdk
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/23.2.8568313
-pushd app/jni
-./build_boost.sh
-./build_openssl.sh
-./build_miniupnpc.sh
+pushd binary/jni
+export BUILD_SO=1
+./build_debug.sh
popd
gradle clean assembleDebug
diff --git a/app/build.gradle b/app/build.gradle
index 9a491fb..1e05076 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -43,7 +43,7 @@ android {
externalNativeBuild {
ndkBuild {
- arguments "NDK_MODULE_PATH:=${rootProject.projectDir}/app/jni"
+ arguments "NDK_MODULE_PATH:=${rootProject.projectDir}/binary/jni"
arguments "-j${Runtime.getRuntime().availableProcessors()}"
}
}
@@ -80,7 +80,13 @@ android {
externalNativeBuild {
ndkBuild {
- path "${rootProject.projectDir}/app/jni/Android.mk"
+ path "${rootProject.projectDir}/binary/jni/Android.mk"
+ }
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDir file("${rootProject.projectDir}/binary/libs")
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e7c2d76..0eab729 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,7 @@
{
- try {
- processAssets();
- I2PD_JNI.loadLibraries();
- //registerNetworkCallback();
- } catch (Throwable tr) {
- lastThrowable = tr;
- setState(State.startFailed);
- return;
- }
- try {
- synchronized (DaemonWrapper.this) {
- I2PD_JNI.setDataDir(i2pdpath); // (Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd");
- Log.i(TAG, "setting webconsole language to " + appLocale);
- I2PD_JNI.setLanguage(appLocale);
+ public synchronized void startDaemonIfStopped(Context ctx) {
+ if (getState() == State.startedOkay) return;
+ new Thread(() -> {
+ synchronized(DaemonWrapper.this) {
+ if (getState() == State.startedOkay) return;
+ try {
+ processAssets();
+ //registerNetworkCallback();
+ } catch (Throwable tr) {
+ lastThrowable = tr;
+ setState(State.startFailed);
+ return;
+ }
+ try {
+ String locale = getAppLocale();
+ Log.i(TAG, "setting webconsole language to " + locale);
- daemonStartResult = I2PD_JNI.startDaemon();
+ daemonStartResult = I2pdApi.startDaemon(ctx, i2pdpath, locale, DaemonWrapper.this);
if ("ok".equals(daemonStartResult)) {
setState(State.startedOkay);
} else
setState(State.startFailed);
+ } catch (Throwable tr) {
+ lastThrowable = tr;
+ setState(State.startFailed);
}
- } catch (Throwable tr) {
- lastThrowable = tr;
- setState(State.startFailed);
}
}, "i2pdDaemonStart").start();
}
@@ -207,72 +210,76 @@ public class DaemonWrapper {
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX
StringBuilder text = new StringBuilder();
Log.d(TAG, "checking assets");
-
- if (holderFile.exists()) {
- try { // if holder file exists, read assets version string
- FileReader fileReader = new FileReader(holderFile);
-
- try {
- BufferedReader br = new BufferedReader(fileReader);
+ try {
+ if (holderFile.exists()) {
+ try { // if holder file exists, read assets version string
+ FileReader fileReader = new FileReader(holderFile);
try {
- String line;
+ BufferedReader br = new BufferedReader(fileReader);
- while ((line = br.readLine()) != null) {
- text.append(line);
+ try {
+ String line;
+
+ while ((line = br.readLine()) != null) {
+ text.append(line);
+ }
+ } finally {
+ try {
+ br.close();
+ } catch (IOException e) {
+ Log.e(TAG, "", e);
+ }
}
- }finally {
+ } finally {
try {
- br.close();
+ fileReader.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);
}
- } 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);
-
- // 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);
+ // if version differs from current app version or null, try to delete certificates folder
+ if (!text.toString().contains(versionName)) {
try {
- writer.append(versionName);
- } finally {
+ 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);
+
+ // 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.close();
- } catch (IOException e) {
- Log.e(TAG,"on writer close", e);
+ 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);
}
}
- catch (Throwable tr)
- {
- Log.e(TAG,"on assets copying", tr);
+ }catch(Throwable tr) {
+ if(tr.getMessage().contains("Permission denied")) {
+ Log.e(TAG, "Permission denied on assets copying", tr);
}
+ throw new RuntimeException(tr);
}
}
@@ -372,15 +379,18 @@ public class DaemonWrapper {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
- I2PD_JNI.onNetworkStateChanged(true);
+ //I2PD_JNI.onNetworkStateChanged(true);
Log.d(TAG, "NetworkCallback.onAvailable");
}
@Override
public void onLost(Network network) {
super.onLost(network);
- I2PD_JNI.onNetworkStateChanged(false);
+ //I2PD_JNI.onNetworkStateChanged(false);
Log.d(TAG, " NetworkCallback.onLost");
}
}
+ private String getAppLocale() {
+ return Locale.getDefault().getDisplayLanguage(Locale.ENGLISH).toLowerCase(); // lower-case system language (like "english")
+ }
}
diff --git a/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java b/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java
index c6b87e6..7d38575 100644
--- a/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java
+++ b/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java
@@ -19,6 +19,11 @@ import android.util.Log;
public class ForegroundService extends Service {
private static final String TAG = "FgService";
private volatile boolean shown;
+
+ public static ForegroundService getInstance() {
+ return instance;
+ }
+
private static ForegroundService instance;
private static volatile DaemonWrapper daemon;
private static final Object initDeinitLock = new Object();
@@ -92,6 +97,10 @@ public class ForegroundService extends Service {
@Override
public void onDestroy() {
+ stop();
+ }
+
+ public void stop() {
cancelNotification();
deinitCheck();
instance = null;
diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java
index 2ebdd15..19c3f98 100644
--- a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java
+++ b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java
@@ -38,6 +38,8 @@ import androidx.core.content.ContextCompat;
import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS;
+import org.purplei2p.i2pd.appscope.App;
+
public class I2PDActivity extends Activity {
private static final String TAG = "i2pdActvt";
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
@@ -45,14 +47,12 @@ public class I2PDActivity extends Activity {
public static final String PACKAGE_URI_SCHEME = "package:";
private TextView textView;
- private CheckBox HTTPProxyState;
+ /*private CheckBox HTTPProxyState;
private CheckBox SOCKSProxyState;
private CheckBox BOBState;
private CheckBox SAMState;
private CheckBox I2CPState;
-
-
- private static volatile DaemonWrapper daemon;
+*/
private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() {
@Override
@@ -60,6 +60,10 @@ public class I2PDActivity extends Activity {
updateStatusText();
}
};
+
+ private DaemonWrapper getDaemon() {
+ return App.getDaemonWrapper();
+ }
private void updateStatusText() {
runOnUiThread(() -> {
@@ -67,23 +71,25 @@ public class I2PDActivity extends Activity {
if (textView == null)
return;
- Throwable tr = daemon.getLastThrowable();
+ Throwable tr = getDaemon().getLastThrowable();
if (tr != null) {
textView.setText(throwableToString(tr));
return;
}
- DaemonWrapper.State state = daemon.getState();
+ DaemonWrapper.State state = getDaemon().getState();
- if (daemon.isStartedOkay()) {
+ if (getDaemon().isStartedOkay()) {
+ /*
HTTPProxyState.setChecked(I2PD_JNI.getHTTPProxyState());
SOCKSProxyState.setChecked(I2PD_JNI.getSOCKSProxyState());
BOBState.setChecked(I2PD_JNI.getBOBState());
SAMState.setChecked(I2PD_JNI.getSAMState());
I2CPState.setChecked(I2PD_JNI.getI2CPState());
+ */
}
- String startResultStr = DaemonWrapper.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : "";
+ String startResultStr = DaemonWrapper.State.startFailed==state ? String.format(": %s", getDaemon().getDaemonStartResult()) : "";
String graceStr = DaemonWrapper.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) {
@@ -113,20 +119,21 @@ public class I2PDActivity extends Activity {
setContentView(R.layout.activity_main);
startService(new Intent(this, ForegroundService.class));
textView = (TextView) findViewById(R.id.appStatusText);
+ /*
HTTPProxyState = (CheckBox) findViewById(R.id.service_httpproxy_box);
SOCKSProxyState = (CheckBox) findViewById(R.id.service_socksproxy_box);
BOBState = (CheckBox) findViewById(R.id.service_bob_box);
SAMState = (CheckBox) findViewById(R.id.service_sam_box);
- I2CPState = (CheckBox) findViewById(R.id.service_i2cp_box);
+ I2CPState = (CheckBox) findViewById(R.id.service_i2cp_box);*/
- if (daemon == null) {
+ /*if (getDaemon() == null) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- daemon = new DaemonWrapper(getAssets(), connectivityManager);
+ getDaemon() = new getDaemon()Wrapper(getAssets(), connectivityManager);
}
- ForegroundService.init(daemon);
+ ForegroundService.init(getDaemon());
- daemon.addStateChangeListener(daemonStateUpdatedListener);
- daemonStateUpdatedListener.daemonStateUpdate(DaemonWrapper.State.uninitialized, daemon.getState());
+ */
+ //getDaemon()StateUpdatedListener.getDaemon()StateUpdate(getDaemon()Wrapper.State.uninitialized, App.getgetDaemon()Wrapper().getState());
// request permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@@ -142,6 +149,11 @@ public class I2PDActivity extends Activity {
}
}
+ //here, we might have datadir access permissions,
+ // it's maybe safe to call daemon start which copies i2pd assets
+ getDaemon().startDaemonIfStopped(getApplicationContext());
+ getDaemon().addStateChangeListener(daemonStateUpdatedListener);
+ updateStatusText();
doBindService();
final Timer gracefulQuitTimer = getGracefulQuitTimer();
@@ -161,7 +173,7 @@ public class I2PDActivity extends Activity {
super.onDestroy();
textView = null;
ForegroundService.deinit();
- daemon.removeStateChangeListener(daemonStateUpdatedListener);
+ getDaemon().removeStateChangeListener(daemonStateUpdatedListener);
//cancelGracefulStop0();
try {
doUnbindService();
@@ -294,15 +306,15 @@ public class I2PDActivity extends Activity {
onActionBatteryOptimizations();
return true;
- case R.id.action_reload_tunnels_config:
+/* case R.id.action_reload_tunnels_config:
onReloadTunnelsConfig();
- return true;
+ return true;*/
case R.id.action_start_webview:
- if(daemon.isStartedOkay())
+ if(getDaemon().isStartedOkay())
startActivity(new Intent(getApplicationContext(), WebConsoleActivity.class));
else
- Toast.makeText(this,"I2Pd not was started!", Toast.LENGTH_SHORT).show();
+ Toast.makeText(this,"I2Pd not started!", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_settings:
startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
@@ -325,7 +337,7 @@ public class I2PDActivity extends Activity {
private void onReloadTunnelsConfig() {
Log.i(TAG, "reloading tunnels");
- daemon.reloadTunnelsConfigs();
+ getDaemon().reloadTunnelsConfigs();
Toast.makeText(this, R.string.tunnels_reloading, Toast.LENGTH_SHORT).show();
}
@@ -335,7 +347,7 @@ public class I2PDActivity extends Activity {
textView.setText(getText(R.string.stopping));
new Thread(() -> {
try {
- daemon.stopDaemon();
+ getDaemon().stopDaemon(null);
} catch (Throwable tr) {
Log.e(TAG, "", tr);
}
@@ -346,7 +358,7 @@ public class I2PDActivity extends Activity {
private static volatile Timer gracefulQuitTimer;
private void i2pdGracefulStop() {
- if (daemon.getState() == DaemonWrapper.State.stopped) {
+ if (getDaemon().getState() == DaemonWrapper.State.stopped) {
Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show();
return;
}
@@ -358,8 +370,8 @@ public class I2PDActivity extends Activity {
Toast.makeText(this, R.string.graceful_stop_is_in_progress, Toast.LENGTH_SHORT).show();
new Thread(() -> {
try {
- if (daemon.isStartedOkay()) {
- daemon.stopAcceptingTunnels();
+ if (getDaemon().isStartedOkay()) {
+ getDaemon().stopAcceptingTunnels();
long gracefulStopAtMillis;
synchronized (graceStartedMillis_LOCK) {
graceStartedMillis = System.currentTimeMillis();
@@ -380,8 +392,8 @@ public class I2PDActivity extends Activity {
Log.i(TAG, "canceling graceful stop");
new Thread(() -> {
try {
- if (daemon.isStartedOkay()) {
- daemon.startAcceptingTunnels();
+ if (getDaemon().isStartedOkay()) {
+ getDaemon().startAcceptingTunnels();
runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show());
} else
i2pdStop();
@@ -395,7 +407,7 @@ public class I2PDActivity extends Activity {
if (gracefulQuitTimerOld != null)
gracefulQuitTimerOld.cancel();
- if (daemon.getTransitTunnelsCount() <= 0) { // no tunnels left
+ if (getDaemon().getTransitTunnelsCount() <= 0) { // no tunnels left
Log.i(TAG, "no transit tunnels left, stopping");
i2pdStop();
return;
@@ -507,7 +519,7 @@ public class I2PDActivity extends Activity {
Log.e(TAG, "", tr);
}
try {
- daemon.stopDaemon();
+ ((App)getApplication()).quit();
} catch (Throwable tr) {
Log.e(TAG, "", tr);
}
diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java b/app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java
deleted file mode 100644
index c1f7d1c..0000000
--- a/app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.purplei2p.i2pd;
-
-public class I2PD_JNI {
- public static native String getABICompiledWith();
-
- public static void loadLibraries() {
- System.loadLibrary("i2pd");
- }
-
- /**
- * returns error info if failed
- * returns "ok" if daemon initialized and started okay
- */
- public static native String startDaemon();
- public static native void stopDaemon();
-
- public static native void startAcceptingTunnels();
- public static native void stopAcceptingTunnels();
- public static native void reloadTunnelsConfigs();
-
- public static native void setDataDir(String jdataDir);
- public static native void setLanguage(String jlanguage);
-
- public static native int getTransitTunnelsCount();
- public static native String getWebConsAddr();
- public static native String getDataDir();
-
- public static native boolean getHTTPProxyState();
- public static native boolean getSOCKSProxyState();
- public static native boolean getBOBState();
- public static native boolean getSAMState();
- public static native boolean getI2CPState();
-
- public static native void onNetworkStateChanged(boolean isConnected);
-}
diff --git a/app/src/main/java/org/purplei2p/i2pd/I2pdApi.java b/app/src/main/java/org/purplei2p/i2pd/I2pdApi.java
new file mode 100644
index 0000000..78d932f
--- /dev/null
+++ b/app/src/main/java/org/purplei2p/i2pd/I2pdApi.java
@@ -0,0 +1,154 @@
+package org.purplei2p.i2pd;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+
+/** i2pd process API calls via TCP between the Android Java app and i2pd C++-only process.
+ * TODO
+ */
+public class I2pdApi {
+ private static String dataDir;
+ private static AbstractProcess i2pdProcess;
+ private static final String TAG = "I2pdApi";
+
+ /**
+ * returns error info if failed
+ * returns "ok" if daemon initialized and started okay
+ */
+ public static String startDaemon(final Context ctx, final String dataDir, String ignoredLanguage, final DaemonWrapper daemonWrapper){
+ try {
+ i2pdProcess = null;
+ I2pdApi.dataDir = dataDir;
+ File pidFile = new File(dataDir, "i2pd.pid");
+ Log.i(TAG,"Launching an i2pd process");
+ final Process p = Runtime.getRuntime().exec(new String[]{
+ "/system/bin/sh",
+ "-c",
+ "ulimit -c unlimited && "+
+ ctx.getApplicationInfo().nativeLibraryDir +
+ "/libi2pd.so --datadir=" + dataDir +
+ " --pidfile=" + pidFile.getAbsolutePath() +
+ " > "+dataDir+"/s.log 2>&1"
+ });
+ i2pdProcess = (Throwable tr) -> {
+ try {
+ if (tr != null)
+ Log.e(TAG, "destroying the subprocess \"i2pd\", reason: " + tr, tr);
+ else
+ Log.e(TAG, "destroying the subprocess \"i2pd\", reason: null");
+ p.destroy();
+ } catch (Throwable tr2) {
+ Log.e(TAG, "", tr2);
+ }
+ };
+ new Thread(() -> {
+ try {
+ try (BufferedInputStream bis = new BufferedInputStream(p.getInputStream())) {
+ try (InputStreamReader sr = new InputStreamReader(bis)) {
+ try (BufferedReader r = new BufferedReader(sr)) {
+ while (true) {
+ String s = r.readLine();
+ if (s == null) break;
+ Log.i(TAG, s);
+ }
+ }
+ }
+ }
+ } catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ }, "i2pd-stdout").start();
+ new Thread(() -> {
+ try {
+ try (BufferedInputStream bis = new BufferedInputStream(p.getErrorStream())) {
+ try (InputStreamReader sr = new InputStreamReader(bis)) {
+ try (BufferedReader r = new BufferedReader(sr)) {
+ while (true) {
+ String s = r.readLine();
+ if (s == null) break;
+ Log.i(TAG, s);
+ }
+ }
+ }
+ }
+ } catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ try {
+ p.waitFor();
+ } catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ final int errorLevel = p.exitValue();
+ Log.i(TAG, "i2pd process exit code: " + errorLevel);
+ final Throwable trReason = new Throwable("subprocess \"i2pd\" exited with exit code " + errorLevel);
+ try {
+ stopDaemon(trReason);
+ Log.i(TAG, "stopDaemon completed");
+ } catch (Throwable tr) {
+ Log.e(TAG, "Called stopDaemon, got exception", tr);
+ }
+ new Thread(() -> {
+ try {
+ daemonWrapper.stopDaemon(trReason);
+ Log.i(TAG, "daemonWrapper.stopDaemon completed");
+ } catch (Throwable tr) {
+ Log.e(TAG, "Called daemonWrapper.stopDaemon, got exception", tr);
+ }
+ }, "stop the daemonWrapper thread").start();
+ }, "i2pd-stderr").start();
+ new Thread(() -> {
+ try {
+// try (BufferedOutputStream bos = new BufferedOutputStream(p.getOutputStream())) {
+// try (OutputStreamWriter sr = new OutputStreamWriter(bos)) {
+// try (BufferedWriter r = new BufferedWriter(sr)) {
+ while (true) {
+ synchronized (Thread.currentThread()) {
+ Thread.currentThread().wait(100);
+ }
+ }
+// }
+// }
+// }
+ } catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ }, "i2pd-stdin").start();
+ return "ok";
+ } catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ return "Error in exec(): " + tr;
+ }
+ }
+
+ public static void stopDaemon(Throwable tr){
+ AbstractProcess p = i2pdProcess;
+ if (p != null) {
+ p.kill(tr);
+ i2pdProcess = null;
+ }
+ }
+
+ public static void startAcceptingTunnels(){}
+ public static void stopAcceptingTunnels(){}
+ public static void reloadTunnelsConfigs(){}
+
+ public static int getTransitTunnelsCount(){return -1;}
+ public static String getWebConsAddr(){return "";}
+ public static String getDataDir() {
+ return dataDir;
+ }
+
+ public static boolean getHTTPProxyState(){return false;}
+ public static boolean getSOCKSProxyState(){return false;}
+ public static boolean getBOBState(){return false;}
+ public static boolean getSAMState(){return false;}
+ public static boolean getI2CPState(){return false;}
+
+ public static void onNetworkStateChanged(boolean isConnected){}
+}
diff --git a/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java b/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java
index 35975a0..27509da 100644
--- a/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java
+++ b/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java
@@ -19,7 +19,7 @@ public class NetworkStateChangeReceiver extends BroadcastReceiver {
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
boolean isConnected = activeNetworkInfo != null && activeNetworkInfo.isConnected();
- I2PD_JNI.onNetworkStateChanged(isConnected);
+ //I2PD_JNI.onNetworkStateChanged(isConnected);
} catch (Throwable tr) {
Log.e(TAG, "", tr);
}
diff --git a/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java b/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java
index e96d068..7df7c2a 100644
--- a/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java
+++ b/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java
@@ -27,7 +27,7 @@ public class WebConsoleActivity extends Activity {
final WebSettings webSettings = webView.getSettings();
webSettings.setBuiltInZoomControls(true);
webSettings.setJavaScriptEnabled(false);
- webView.loadUrl(I2PD_JNI.getWebConsAddr());
+ webView.loadUrl("http://localhost:7070/"/*I2PD_JNI.getWebConsAddr()*/);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
diff --git a/app/src/main/java/org/purplei2p/i2pd/appscope/App.java b/app/src/main/java/org/purplei2p/i2pd/appscope/App.java
new file mode 100644
index 0000000..d624b96
--- /dev/null
+++ b/app/src/main/java/org/purplei2p/i2pd/appscope/App.java
@@ -0,0 +1,126 @@
+package org.purplei2p.i2pd.appscope;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.ConnectivityManager;
+import android.os.IBinder;
+import android.util.Log;
+
+import org.purplei2p.i2pd.BuildConfig;
+import org.purplei2p.i2pd.I2PDActivity;
+import org.purplei2p.i2pd.*;
+
+public class App extends Application {
+ private static final String TAG = "i2pd.app";
+
+ //private static final I2PD_JNI jniHolder = new I2PD_JNI();
+
+ private static volatile DaemonWrapper daemonWrapper;
+ private String versionName;
+
+ private static volatile boolean mIsBound;
+
+
+
+ public synchronized static DaemonWrapper getDaemonWrapper() {
+ return daemonWrapper;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ synchronized (this) {
+ if (getDaemonWrapper() == null) {
+ createDaemonWrapper();
+ }
+ versionName = BuildConfig.VERSION_NAME;
+ doBindService();
+ startService(new Intent(this, ForegroundService.class));
+ }
+ }
+
+ private void createDaemonWrapper() {
+ ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ daemonWrapper = new DaemonWrapper(getApplicationContext(), getAssets(), connectivityManager);
+ ForegroundService.init(daemonWrapper);
+ }
+
+ private synchronized void doBindService() {
+ 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 synchronized void doUnbindService() {
+ if (mIsBound) {
+ // Detach our existing connection.
+ unbindService(mConnection);
+ mIsBound = false;
+ }
+ }
+
+ @Override
+ public void onTerminate() {
+ quit();
+ super.onTerminate();
+ }
+
+ private final 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();
+ }
+ };
+
+ public synchronized void quit() {
+ try {
+ if(daemonWrapper!=null)daemonWrapper.stopDaemon(null);
+ } catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ try {
+ doUnbindService();
+ } catch (IllegalArgumentException ex) {
+ Log.e(TAG, "throwable caught and ignored", ex);
+ if (ex.getMessage().startsWith("Service not registered: " + I2PDActivity.class.getName())) {
+ Log.i(TAG, "Service not registered exception seems to be normal, not a bug it seems.");
+ }
+ } catch (Throwable tr) {
+ Log.e(TAG, "throwable caught and ignored", tr);
+ }
+ try{
+ ForegroundService fs = ForegroundService.getInstance();
+ if(fs!=null)fs.stop();
+ }catch(Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+
+ }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 793a9ae..805bdd6 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -58,7 +58,7 @@
-
+
diff --git a/app/src/main/res/menu/options_main.xml b/app/src/main/res/menu/options_main.xml
index 6857d41..c1157a4 100644
--- a/app/src/main/res/menu/options_main.xml
+++ b/app/src/main/res/menu/options_main.xml
@@ -15,10 +15,10 @@
android:id="@+id/action_start_webview"
android:orderInCategory="96"
android:title="@string/action_start_webview" />
-
+ android:title="@string/action_reload_tunnels_config" />-->