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" />-->