Browse Source

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
pull/82/head
nonlin-lin-chaos-order-etc-etal 4 months ago committed by GitHub
parent
commit
988940f50a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      README.md
  2. 10
      app/build.gradle
  3. 1
      app/src/main/AndroidManifest.xml
  4. 7
      app/src/main/java/org/purplei2p/i2pd/AbstractProcess.java
  5. 172
      app/src/main/java/org/purplei2p/i2pd/DaemonWrapper.java
  6. 9
      app/src/main/java/org/purplei2p/i2pd/ForegroundService.java
  7. 68
      app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java
  8. 35
      app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java
  9. 154
      app/src/main/java/org/purplei2p/i2pd/I2pdApi.java
  10. 2
      app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java
  11. 2
      app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java
  12. 126
      app/src/main/java/org/purplei2p/i2pd/appscope/App.java
  13. 4
      app/src/main/res/layout/activity_main.xml
  14. 4
      app/src/main/res/menu/options_main.xml
  15. 23
      binary/jni/build_debug.sh
  16. 20
      binary/jni/build_release.sh
  17. 16
      binary/jni/ndkbuild-wrapper.sh
  18. 1
      gradle.properties

7
README.md

@ -53,10 +53,9 @@ export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export ANDROID_HOME=/opt/android-sdk export ANDROID_HOME=/opt/android-sdk
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/23.2.8568313 export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/23.2.8568313
pushd app/jni pushd binary/jni
./build_boost.sh export BUILD_SO=1
./build_openssl.sh ./build_debug.sh
./build_miniupnpc.sh
popd popd
gradle clean assembleDebug gradle clean assembleDebug

10
app/build.gradle

@ -43,7 +43,7 @@ android {
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
arguments "NDK_MODULE_PATH:=${rootProject.projectDir}/app/jni" arguments "NDK_MODULE_PATH:=${rootProject.projectDir}/binary/jni"
arguments "-j${Runtime.getRuntime().availableProcessors()}" arguments "-j${Runtime.getRuntime().availableProcessors()}"
} }
} }
@ -80,7 +80,13 @@ android {
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
path "${rootProject.projectDir}/app/jni/Android.mk" path "${rootProject.projectDir}/binary/jni/Android.mk"
}
}
sourceSets {
main {
jniLibs.srcDir file("${rootProject.projectDir}/binary/libs")
} }
} }

1
app/src/main/AndroidManifest.xml

@ -16,6 +16,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application <application
android:name=".appscope.App"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/logo" android:icon="@mipmap/logo"
android:label="@string/app_name" android:label="@string/app_name"

7
app/src/main/java/org/purplei2p/i2pd/AbstractProcess.java

@ -0,0 +1,7 @@
package org.purplei2p.i2pd;
public interface AbstractProcess {
/** @param tr can be null
*/
void kill(Throwable tr);
}

172
app/src/main/java/org/purplei2p/i2pd/DaemonWrapper.java

@ -13,6 +13,7 @@ import java.util.Set;
import java.util.Locale; import java.util.Locale;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Network; import android.net.Network;
@ -67,25 +68,25 @@ public class DaemonWrapper {
public synchronized void stopAcceptingTunnels() { public synchronized void stopAcceptingTunnels() {
if (isStartedOkay()) { if (isStartedOkay()) {
setState(State.gracefulShutdownInProgress); setState(State.gracefulShutdownInProgress);
I2PD_JNI.stopAcceptingTunnels(); //I2PD_JNI.stopAcceptingTunnels();
} }
} }
public synchronized void startAcceptingTunnels() { public synchronized void startAcceptingTunnels() {
if (isStartedOkay()) { if (isStartedOkay()) {
setState(State.startedOkay); setState(State.startedOkay);
I2PD_JNI.startAcceptingTunnels(); //I2PD_JNI.startAcceptingTunnels();
} }
} }
public synchronized void reloadTunnelsConfigs() { public synchronized void reloadTunnelsConfigs() {
if (isStartedOkay()) { if (isStartedOkay()) {
I2PD_JNI.reloadTunnelsConfigs(); //I2PD_JNI.reloadTunnelsConfigs();
} }
} }
public int getTransitTunnelsCount() { public int getTransitTunnelsCount() {
return I2PD_JNI.getTransitTunnelsCount(); return 0;//I2PD_JNI.getTransitTunnelsCount();
} }
public enum State { public enum State {
@ -117,11 +118,11 @@ public class DaemonWrapper {
return state; return state;
} }
public DaemonWrapper(AssetManager assetManager, ConnectivityManager connectivityManager){ public DaemonWrapper(Context ctx, AssetManager assetManager, ConnectivityManager connectivityManager){
this.assetManager = assetManager; this.assetManager = assetManager;
this.connectivityManager = connectivityManager; this.connectivityManager = connectivityManager;
setState(State.starting); setState(State.starting);
startDaemon(); //startDaemon(ctx); //need to start when storage permissions to the datadir exist
} }
private Throwable lastThrowable; private Throwable lastThrowable;
@ -147,11 +148,13 @@ public class DaemonWrapper {
} }
public static String getDataDir() { // for settings iniEditor public static String getDataDir() { // for settings iniEditor
return I2PD_JNI.getDataDir(); return i2pdDataDir;
} }
private static String i2pdDataDir;
public void changeDataDir(String dataDir, Boolean updateAssets) { public void changeDataDir(String dataDir, Boolean updateAssets) {
I2PD_JNI.setDataDir(dataDir); i2pdDataDir=dataDir;
if (updateAssets) processAssets(); if (updateAssets) processAssets();
//ToDo: move old dir to new dir? //ToDo: move old dir to new dir?
} }
@ -160,44 +163,44 @@ public class DaemonWrapper {
return getState().isStartedOkay(); return getState().isStartedOkay();
} }
public synchronized void stopDaemon() { public synchronized void stopDaemon(final Throwable throwable) {
if (isStartedOkay()) { if (isStartedOkay()) {
try { try {
I2PD_JNI.stopDaemon(); I2pdApi.stopDaemon(throwable);
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG, "", tr); Log.e(TAG, "", tr);
} }
if (throwable != null) lastThrowable = throwable;
setState(State.stopped); setState(State.stopped);
} }
} }
public synchronized void startDaemon() {
if( getState() != State.stopped && getState() != State.starting ) return;
new Thread(() -> {
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); public synchronized void startDaemonIfStopped(Context ctx) {
I2PD_JNI.setLanguage(appLocale); 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)) { if ("ok".equals(daemonStartResult)) {
setState(State.startedOkay); setState(State.startedOkay);
} else } else
setState(State.startFailed); setState(State.startFailed);
} catch (Throwable tr) {
lastThrowable = tr;
setState(State.startFailed);
} }
} catch (Throwable tr) {
lastThrowable = tr;
setState(State.startFailed);
} }
}, "i2pdDaemonStart").start(); }, "i2pdDaemonStart").start();
} }
@ -207,72 +210,76 @@ public class DaemonWrapper {
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
Log.d(TAG, "checking assets"); Log.d(TAG, "checking assets");
try {
if (holderFile.exists()) { if (holderFile.exists()) {
try { // if holder file exists, read assets version string try { // if holder file exists, read assets version string
FileReader fileReader = new FileReader(holderFile); FileReader fileReader = new FileReader(holderFile);
try {
BufferedReader br = new BufferedReader(fileReader);
try { try {
String line; BufferedReader br = new BufferedReader(fileReader);
while ((line = br.readLine()) != null) { try {
text.append(line); String line;
while ((line = br.readLine()) != null) {
text.append(line);
}
} finally {
try {
br.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
} }
}finally { } finally {
try { try {
br.close(); fileReader.close();
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "", e); Log.e(TAG, "", e);
} }
} }
} finally { } catch (IOException e) {
try { Log.e(TAG, "", e);
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 version differs from current app version or null, try to delete certificates folder
if (!text.toString().contains(versionName)) { 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);
try { try {
writer.append(versionName); boolean deleteResult = holderFile.delete();
} finally { 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 { try {
writer.close(); writer.append(versionName);
} catch (IOException e) { } finally {
Log.e(TAG,"on writer close", e); 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) }catch(Throwable tr) {
{ if(tr.getMessage().contains("Permission denied")) {
Log.e(TAG,"on assets copying", tr); Log.e(TAG, "Permission denied on assets copying", tr);
} }
throw new RuntimeException(tr);
} }
} }
@ -372,15 +379,18 @@ public class DaemonWrapper {
@Override @Override
public void onAvailable(Network network) { public void onAvailable(Network network) {
super.onAvailable(network); super.onAvailable(network);
I2PD_JNI.onNetworkStateChanged(true); //I2PD_JNI.onNetworkStateChanged(true);
Log.d(TAG, "NetworkCallback.onAvailable"); Log.d(TAG, "NetworkCallback.onAvailable");
} }
@Override @Override
public void onLost(Network network) { public void onLost(Network network) {
super.onLost(network); super.onLost(network);
I2PD_JNI.onNetworkStateChanged(false); //I2PD_JNI.onNetworkStateChanged(false);
Log.d(TAG, " NetworkCallback.onLost"); Log.d(TAG, " NetworkCallback.onLost");
} }
} }
private String getAppLocale() {
return Locale.getDefault().getDisplayLanguage(Locale.ENGLISH).toLowerCase(); // lower-case system language (like "english")
}
} }

9
app/src/main/java/org/purplei2p/i2pd/ForegroundService.java

@ -19,6 +19,11 @@ import android.util.Log;
public class ForegroundService extends Service { public class ForegroundService extends Service {
private static final String TAG = "FgService"; private static final String TAG = "FgService";
private volatile boolean shown; private volatile boolean shown;
public static ForegroundService getInstance() {
return instance;
}
private static ForegroundService instance; private static ForegroundService instance;
private static volatile DaemonWrapper daemon; private static volatile DaemonWrapper daemon;
private static final Object initDeinitLock = new Object(); private static final Object initDeinitLock = new Object();
@ -92,6 +97,10 @@ public class ForegroundService extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
stop();
}
public void stop() {
cancelNotification(); cancelNotification();
deinitCheck(); deinitCheck();
instance = null; instance = null;

68
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 static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS;
import org.purplei2p.i2pd.appscope.App;
public class I2PDActivity extends Activity { public class I2PDActivity extends Activity {
private static final String TAG = "i2pdActvt"; private static final String TAG = "i2pdActvt";
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1; 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:"; public static final String PACKAGE_URI_SCHEME = "package:";
private TextView textView; private TextView textView;
private CheckBox HTTPProxyState; /*private CheckBox HTTPProxyState;
private CheckBox SOCKSProxyState; private CheckBox SOCKSProxyState;
private CheckBox BOBState; private CheckBox BOBState;
private CheckBox SAMState; private CheckBox SAMState;
private CheckBox I2CPState; private CheckBox I2CPState;
*/
private static volatile DaemonWrapper daemon;
private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() { private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() {
@Override @Override
@ -60,6 +60,10 @@ public class I2PDActivity extends Activity {
updateStatusText(); updateStatusText();
} }
}; };
private DaemonWrapper getDaemon() {
return App.getDaemonWrapper();
}
private void updateStatusText() { private void updateStatusText() {
runOnUiThread(() -> { runOnUiThread(() -> {
@ -67,23 +71,25 @@ public class I2PDActivity extends Activity {
if (textView == null) if (textView == null)
return; return;
Throwable tr = daemon.getLastThrowable(); Throwable tr = getDaemon().getLastThrowable();
if (tr != null) { if (tr != null) {
textView.setText(throwableToString(tr)); textView.setText(throwableToString(tr));
return; return;
} }
DaemonWrapper.State state = daemon.getState(); DaemonWrapper.State state = getDaemon().getState();
if (daemon.isStartedOkay()) { if (getDaemon().isStartedOkay()) {
/*
HTTPProxyState.setChecked(I2PD_JNI.getHTTPProxyState()); HTTPProxyState.setChecked(I2PD_JNI.getHTTPProxyState());
SOCKSProxyState.setChecked(I2PD_JNI.getSOCKSProxyState()); SOCKSProxyState.setChecked(I2PD_JNI.getSOCKSProxyState());
BOBState.setChecked(I2PD_JNI.getBOBState()); BOBState.setChecked(I2PD_JNI.getBOBState());
SAMState.setChecked(I2PD_JNI.getSAMState()); SAMState.setChecked(I2PD_JNI.getSAMState());
I2CPState.setChecked(I2PD_JNI.getI2CPState()); 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)) : ""; 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)); textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr));
} catch (Throwable tr) { } catch (Throwable tr) {
@ -113,20 +119,21 @@ public class I2PDActivity extends Activity {
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
startService(new Intent(this, ForegroundService.class)); startService(new Intent(this, ForegroundService.class));
textView = (TextView) findViewById(R.id.appStatusText); textView = (TextView) findViewById(R.id.appStatusText);
/*
HTTPProxyState = (CheckBox) findViewById(R.id.service_httpproxy_box); HTTPProxyState = (CheckBox) findViewById(R.id.service_httpproxy_box);
SOCKSProxyState = (CheckBox) findViewById(R.id.service_socksproxy_box); SOCKSProxyState = (CheckBox) findViewById(R.id.service_socksproxy_box);
BOBState = (CheckBox) findViewById(R.id.service_bob_box); BOBState = (CheckBox) findViewById(R.id.service_bob_box);
SAMState = (CheckBox) findViewById(R.id.service_sam_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); 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 // request permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 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(); doBindService();
final Timer gracefulQuitTimer = getGracefulQuitTimer(); final Timer gracefulQuitTimer = getGracefulQuitTimer();
@ -161,7 +173,7 @@ public class I2PDActivity extends Activity {
super.onDestroy(); super.onDestroy();
textView = null; textView = null;
ForegroundService.deinit(); ForegroundService.deinit();
daemon.removeStateChangeListener(daemonStateUpdatedListener); getDaemon().removeStateChangeListener(daemonStateUpdatedListener);
//cancelGracefulStop0(); //cancelGracefulStop0();
try { try {
doUnbindService(); doUnbindService();
@ -294,15 +306,15 @@ public class I2PDActivity extends Activity {
onActionBatteryOptimizations(); onActionBatteryOptimizations();
return true; return true;
case R.id.action_reload_tunnels_config: /* case R.id.action_reload_tunnels_config:
onReloadTunnelsConfig(); onReloadTunnelsConfig();
return true; return true;*/
case R.id.action_start_webview: case R.id.action_start_webview:
if(daemon.isStartedOkay()) if(getDaemon().isStartedOkay())
startActivity(new Intent(getApplicationContext(), WebConsoleActivity.class)); startActivity(new Intent(getApplicationContext(), WebConsoleActivity.class));
else else
Toast.makeText(this,"I2Pd not was started!", Toast.LENGTH_SHORT).show(); Toast.makeText(this,"I2Pd not started!", Toast.LENGTH_SHORT).show();
return true; return true;
case R.id.action_settings: case R.id.action_settings:
startActivity(new Intent(getApplicationContext(), SettingsActivity.class)); startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
@ -325,7 +337,7 @@ public class I2PDActivity extends Activity {
private void onReloadTunnelsConfig() { private void onReloadTunnelsConfig() {
Log.i(TAG, "reloading tunnels"); Log.i(TAG, "reloading tunnels");
daemon.reloadTunnelsConfigs(); getDaemon().reloadTunnelsConfigs();
Toast.makeText(this, R.string.tunnels_reloading, Toast.LENGTH_SHORT).show(); 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)); textView.setText(getText(R.string.stopping));
new Thread(() -> { new Thread(() -> {
try { try {
daemon.stopDaemon(); getDaemon().stopDaemon(null);
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG, "", tr); Log.e(TAG, "", tr);
} }
@ -346,7 +358,7 @@ public class I2PDActivity extends Activity {
private static volatile Timer gracefulQuitTimer; private static volatile Timer gracefulQuitTimer;
private void i2pdGracefulStop() { 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(); Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show();
return; return;
} }
@ -358,8 +370,8 @@ public class I2PDActivity extends Activity {
Toast.makeText(this, R.string.graceful_stop_is_in_progress, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.graceful_stop_is_in_progress, Toast.LENGTH_SHORT).show();
new Thread(() -> { new Thread(() -> {
try { try {
if (daemon.isStartedOkay()) { if (getDaemon().isStartedOkay()) {
daemon.stopAcceptingTunnels(); getDaemon().stopAcceptingTunnels();
long gracefulStopAtMillis; long gracefulStopAtMillis;
synchronized (graceStartedMillis_LOCK) { synchronized (graceStartedMillis_LOCK) {
graceStartedMillis = System.currentTimeMillis(); graceStartedMillis = System.currentTimeMillis();
@ -380,8 +392,8 @@ public class I2PDActivity extends Activity {
Log.i(TAG, "canceling graceful stop"); Log.i(TAG, "canceling graceful stop");
new Thread(() -> { new Thread(() -> {
try { try {
if (daemon.isStartedOkay()) { if (getDaemon().isStartedOkay()) {
daemon.startAcceptingTunnels(); getDaemon().startAcceptingTunnels();
runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show()); runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show());
} else } else
i2pdStop(); i2pdStop();
@ -395,7 +407,7 @@ public class I2PDActivity extends Activity {
if (gracefulQuitTimerOld != null) if (gracefulQuitTimerOld != null)
gracefulQuitTimerOld.cancel(); gracefulQuitTimerOld.cancel();
if (daemon.getTransitTunnelsCount() <= 0) { // no tunnels left if (getDaemon().getTransitTunnelsCount() <= 0) { // no tunnels left
Log.i(TAG, "no transit tunnels left, stopping"); Log.i(TAG, "no transit tunnels left, stopping");
i2pdStop(); i2pdStop();
return; return;
@ -507,7 +519,7 @@ public class I2PDActivity extends Activity {
Log.e(TAG, "", tr); Log.e(TAG, "", tr);
} }
try { try {
daemon.stopDaemon(); ((App)getApplication()).quit();
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG, "", tr); Log.e(TAG, "", tr);
} }

35
app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java

@ -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);
}

154
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){}
}

2
app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java

@ -19,7 +19,7 @@ public class NetworkStateChangeReceiver extends BroadcastReceiver {
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
boolean isConnected = activeNetworkInfo != null && activeNetworkInfo.isConnected(); boolean isConnected = activeNetworkInfo != null && activeNetworkInfo.isConnected();
I2PD_JNI.onNetworkStateChanged(isConnected); //I2PD_JNI.onNetworkStateChanged(isConnected);
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG, "", tr); Log.e(TAG, "", tr);
} }

2
app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java

@ -27,7 +27,7 @@ public class WebConsoleActivity extends Activity {
final WebSettings webSettings = webView.getSettings(); final WebSettings webSettings = webView.getSettings();
webSettings.setBuiltInZoomControls(true); webSettings.setBuiltInZoomControls(true);
webSettings.setJavaScriptEnabled(false); webSettings.setJavaScriptEnabled(false);
webView.loadUrl(I2PD_JNI.getWebConsAddr()); webView.loadUrl("http://localhost:7070/"/*I2PD_JNI.getWebConsAddr()*/);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe); swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {

126
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);
}
}
}

4
app/src/main/res/layout/activity_main.xml

@ -58,7 +58,7 @@
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/margin_medium" /> android:layout_height="@dimen/margin_medium" />
<!--
<TextView <TextView
android:id="@+id/textView" android:id="@+id/textView"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -149,7 +149,7 @@
android:textColor="#DFDFDF" /> android:textColor="#DFDFDF" />
</TableRow> </TableRow>
</TableLayout> </TableLayout>
-->
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

4
app/src/main/res/menu/options_main.xml

@ -15,10 +15,10 @@
android:id="@+id/action_start_webview" android:id="@+id/action_start_webview"
android:orderInCategory="96" android:orderInCategory="96"
android:title="@string/action_start_webview" /> android:title="@string/action_start_webview" />
<item <!--<item
android:id="@+id/action_reload_tunnels_config" android:id="@+id/action_reload_tunnels_config"
android:orderInCategory="97" android:orderInCategory="97"
android:title="@string/action_reload_tunnels_config" /> android:title="@string/action_reload_tunnels_config" />-->
<item <item
android:id="@+id/action_graceful_stop" android:id="@+id/action_graceful_stop"
android:orderInCategory="98" android:orderInCategory="98"

23
binary/jni/build_debug.sh

@ -0,0 +1,23 @@
export I2PD_DEBUG=1
if [ -z "$ANDROID_NDK_HOME" -a "$ANDROID_NDK_HOME" == "" ]; then
echo -e "\033[31mFailed! ANDROID_NDK_HOME is empty. Run 'export ANDROID_NDK_HOME=[PATH_TO_NDK]'\033[0m"
exit 1
fi
echo Building boost...
./build_boost.sh
echo Building miniupnpc...
./build_miniupnpc.sh
echo Building openssl...
./build_openssl.sh
echo Building i2pd...
pushd .
NDK_MODULE_PATH=`pwd`
cd ..
NDK_PROJECT_PATH=`pwd`
popd
if [ -z "$BUILD_SO" -a "$BUILD_SO" == "" ]; then
export NDK_MODULE_PATH=$NDK_MODULE_PATH && export NDK_PROJECT_PATH=$NDK_PROJECT_PATH && $ANDROID_NDK_HOME/ndk-build V=1 NDK_LOG=1 -j`nproc` NDK_DEBUG=1
else
export NDK_MODULE_PATH=$NDK_MODULE_PATH && export NDK_PROJECT_PATH=$NDK_PROJECT_PATH && ./ndkbuild-wrapper.sh V=1 NDK_LOG=1 -j`nproc` NDK_DEBUG=1
fi
echo "$0 completed."

20
binary/jni/build_release.sh

@ -0,0 +1,20 @@
if [ -z "$ANDROID_NDK_HOME" -a "$ANDROID_NDK_HOME" == "" ]; then
echo -e "\033[31mFailed! ANDROID_NDK_HOME is empty. Run 'export ANDROID_NDK_HOME=[PATH_TO_NDK]'\033[0m"
exit 1
fi
echo Building boost...
./build_boost.sh
echo Building miniupnpc...
./build_miniupnpc.sh
echo Building openssl...
./build_openssl.sh
echo Building i2pd...
NDK_MODULE_PATH=`pwd`
cd ..
NDK_PROJECT_PATH=`pwd`
if [ -z "$BUILD_SO" -a "$BUILD_SO" == "" ]; then
export NDK_MODULE_PATH=$NDK_MODULE_PATH && export NDK_PROJECT_PATH=$NDK_PROJECT_PATH && $ANDROID_NDK_HOME/ndk-build V=1 NDK_LOG=1 -j`nproc`
else
export NDK_MODULE_PATH=$NDK_MODULE_PATH && export NDK_PROJECT_PATH=$NDK_PROJECT_PATH && ./ndkbuild-wrapper.sh V=1 NDK_LOG=1 -j`nproc`
fi
echo "$0 completed."

16
binary/jni/ndkbuild-wrapper.sh

@ -0,0 +1,16 @@
#!/usr/bin/env bash
cd $NDK_PROJECT_PATH/jni
$ANDROID_NDK_HOME/ndk-build $*
# if it doesn't exist, then create
if [ ! -d $NDK_PROJECT_PATH/libs ]; then mkdir $NDK_PROJECT_PATH/libs; fi
cd $NDK_PROJECT_PATH/libs
for f in $(ls .);
do
if [ -z "$I2PD_DEBUG" -a "$I2PD_DEBUG" == "" ]; then
strip $f/i2pd
fi
mv $f/i2pd $f/libi2pd.so
done

1
gradle.properties

@ -1,3 +1,4 @@
android.enableJetifier=true android.enableJetifier=true
android.useAndroidX=true android.useAndroidX=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

Loading…
Cancel
Save