mirror of
https://github.com/PurpleI2P/i2pd-android.git
synced 2025-03-10 12:31:41 +00:00
* 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
This commit is contained in:
parent
8dba5a00e5
commit
988940f50a
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:name=".appscope.App"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/logo"
|
||||
android:label="@string/app_name"
|
||||
|
@ -0,0 +1,7 @@
|
||||
package org.purplei2p.i2pd;
|
||||
|
||||
public interface AbstractProcess {
|
||||
/** @param tr can be null
|
||||
*/
|
||||
void kill(Throwable tr);
|
||||
}
|
@ -13,6 +13,7 @@ import java.util.Set;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
@ -67,25 +68,25 @@ public class DaemonWrapper {
|
||||
public synchronized void stopAcceptingTunnels() {
|
||||
if (isStartedOkay()) {
|
||||
setState(State.gracefulShutdownInProgress);
|
||||
I2PD_JNI.stopAcceptingTunnels();
|
||||
//I2PD_JNI.stopAcceptingTunnels();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void startAcceptingTunnels() {
|
||||
if (isStartedOkay()) {
|
||||
setState(State.startedOkay);
|
||||
I2PD_JNI.startAcceptingTunnels();
|
||||
//I2PD_JNI.startAcceptingTunnels();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadTunnelsConfigs() {
|
||||
if (isStartedOkay()) {
|
||||
I2PD_JNI.reloadTunnelsConfigs();
|
||||
//I2PD_JNI.reloadTunnelsConfigs();
|
||||
}
|
||||
}
|
||||
|
||||
public int getTransitTunnelsCount() {
|
||||
return I2PD_JNI.getTransitTunnelsCount();
|
||||
return 0;//I2PD_JNI.getTransitTunnelsCount();
|
||||
}
|
||||
|
||||
public enum State {
|
||||
@ -117,11 +118,11 @@ public class DaemonWrapper {
|
||||
return state;
|
||||
}
|
||||
|
||||
public DaemonWrapper(AssetManager assetManager, ConnectivityManager connectivityManager){
|
||||
public DaemonWrapper(Context ctx, AssetManager assetManager, ConnectivityManager connectivityManager){
|
||||
this.assetManager = assetManager;
|
||||
this.connectivityManager = connectivityManager;
|
||||
setState(State.starting);
|
||||
startDaemon();
|
||||
//startDaemon(ctx); //need to start when storage permissions to the datadir exist
|
||||
}
|
||||
|
||||
private Throwable lastThrowable;
|
||||
@ -147,11 +148,13 @@ public class DaemonWrapper {
|
||||
}
|
||||
|
||||
public static String getDataDir() { // for settings iniEditor
|
||||
return I2PD_JNI.getDataDir();
|
||||
return i2pdDataDir;
|
||||
}
|
||||
|
||||
private static String i2pdDataDir;
|
||||
|
||||
public void changeDataDir(String dataDir, Boolean updateAssets) {
|
||||
I2PD_JNI.setDataDir(dataDir);
|
||||
i2pdDataDir=dataDir;
|
||||
if (updateAssets) processAssets();
|
||||
//ToDo: move old dir to new dir?
|
||||
}
|
||||
@ -160,44 +163,44 @@ public class DaemonWrapper {
|
||||
return getState().isStartedOkay();
|
||||
}
|
||||
|
||||
public synchronized void stopDaemon() {
|
||||
public synchronized void stopDaemon(final Throwable throwable) {
|
||||
if (isStartedOkay()) {
|
||||
try {
|
||||
I2PD_JNI.stopDaemon();
|
||||
I2pdApi.stopDaemon(throwable);
|
||||
} catch (Throwable tr) {
|
||||
Log.e(TAG, "", tr);
|
||||
}
|
||||
if (throwable != null) lastThrowable = throwable;
|
||||
setState(State.stopped);
|
||||
}
|
||||
}
|
||||
public synchronized void startDaemon() {
|
||||
if( getState() != State.stopped && getState() != State.starting ) return;
|
||||
|
||||
public synchronized void startDaemonIfStopped(Context ctx) {
|
||||
if (getState() == State.startedOkay) 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");
|
||||
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);
|
||||
|
||||
Log.i(TAG, "setting webconsole language to " + appLocale);
|
||||
I2PD_JNI.setLanguage(appLocale);
|
||||
|
||||
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);
|
||||
}
|
||||
}finally {
|
||||
try {
|
||||
br.close();
|
||||
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);
|
||||
}
|
||||
}
|
||||
} 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")
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
Normal file
154
app/src/main/java/org/purplei2p/i2pd/I2pdApi.java
Normal file
@ -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){}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
126
app/src/main/java/org/purplei2p/i2pd/appscope/App.java
Normal file
126
app/src/main/java/org/purplei2p/i2pd/appscope/App.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/margin_medium" />
|
||||
|
||||
<!--
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
@ -149,7 +149,7 @@
|
||||
android:textColor="#DFDFDF" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
-->
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
@ -15,10 +15,10 @@
|
||||
android:id="@+id/action_start_webview"
|
||||
android:orderInCategory="96"
|
||||
android:title="@string/action_start_webview" />
|
||||
<item
|
||||
<!--<item
|
||||
android:id="@+id/action_reload_tunnels_config"
|
||||
android:orderInCategory="97"
|
||||
android:title="@string/action_reload_tunnels_config" />
|
||||
android:title="@string/action_reload_tunnels_config" />-->
|
||||
<item
|
||||
android:id="@+id/action_graceful_stop"
|
||||
android:orderInCategory="98"
|
||||
|
23
binary/jni/build_debug.sh
Executable file
23
binary/jni/build_debug.sh
Executable file
@ -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
Executable file
20
binary/jni/build_release.sh
Executable file
@ -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
Executable file
16
binary/jni/ndkbuild-wrapper.sh
Executable file
@ -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,3 +1,4 @@
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
Loading…
x
Reference in New Issue
Block a user