diff --git a/android/.gitignore b/android/.gitignore
index 8364f857..1ecaafbe 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -1,4 +1,7 @@
-/gen/
-/libs/
-/tests/
+gen
+tests
.idea
+local.properties
+build.sh
+bin
+log*
\ No newline at end of file
diff --git a/android/build.xml b/android/build.xml
new file mode 100644
index 00000000..23e9f065
--- /dev/null
+++ b/android/build.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/libs/.gitignore b/android/libs/.gitignore
new file mode 100644
index 00000000..e4e4e6c1
--- /dev/null
+++ b/android/libs/.gitignore
@@ -0,0 +1 @@
+armeabi-v7a
diff --git a/android/libs/android-support-v4.jar b/android/libs/android-support-v4.jar
new file mode 100644
index 00000000..2ff47f4f
Binary files /dev/null and b/android/libs/android-support-v4.jar differ
diff --git a/android/proguard-project.txt b/android/proguard-project.txt
new file mode 100644
index 00000000..f2fe1559
--- /dev/null
+++ b/android/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java
new file mode 100644
index 00000000..7f9aae75
--- /dev/null
+++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java
@@ -0,0 +1,108 @@
+package org.purplei2p.i2pd;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import android.util.Log;
+
+public class DaemonSingleton {
+ private static final String TAG="i2pd";
+ private static final DaemonSingleton instance = new DaemonSingleton();
+ public static interface StateChangeListener { void daemonStateChanged(); }
+ private final Set stateChangeListeners = new HashSet();
+
+ public static DaemonSingleton getInstance() {
+ return instance;
+ }
+
+ public synchronized void addStateChangeListener(StateChangeListener listener) { stateChangeListeners.add(listener); }
+ public synchronized void removeStateChangeListener(StateChangeListener listener) { stateChangeListeners.remove(listener); }
+
+ public void stopAcceptingTunnels() {
+ if(isStartedOkay()){
+ state=State.gracefulShutdownInProgress;
+ fireStateChange();
+ I2PD_JNI.stopAcceptingTunnels();
+ }
+ }
+
+ public void onNetworkStateChange(boolean isConnected) {
+ I2PD_JNI.onNetworkStateChanged(isConnected);
+ }
+
+ private boolean startedOkay;
+
+ public static enum State {starting,jniLibraryLoaded,startedOkay,startFailed,gracefulShutdownInProgress};
+
+ private State state = State.starting;
+
+ public State getState() { return state; }
+
+ {
+ fireStateChange();
+ new Thread(new Runnable(){
+
+ @Override
+ public void run() {
+ try {
+ I2PD_JNI.loadLibraries();
+ state = State.jniLibraryLoaded;
+ fireStateChange();
+ } catch (Throwable tr) {
+ lastThrowable=tr;
+ state = State.startFailed;
+ fireStateChange();
+ return;
+ }
+ try {
+ daemonStartResult = I2PD_JNI.startDaemon();
+ if("ok".equals(daemonStartResult)){state=State.startedOkay;setStartedOkay(true);}
+ else state=State.startFailed;
+ fireStateChange();
+ } catch (Throwable tr) {
+ lastThrowable=tr;
+ state = State.startFailed;
+ fireStateChange();
+ return;
+ }
+ }
+
+ }, "i2pdDaemonStart").start();
+ }
+ private Throwable lastThrowable;
+ private String daemonStartResult="N/A";
+
+ private synchronized void fireStateChange() {
+ Log.i(TAG, "daemon state change: "+state);
+ for(StateChangeListener listener : stateChangeListeners) {
+ try {
+ listener.daemonStateChanged();
+ } catch (Throwable tr) {
+ Log.e(TAG, "exception in listener ignored", tr);
+ }
+ }
+ }
+
+ public Throwable getLastThrowable() {
+ return lastThrowable;
+ }
+
+ public String getDaemonStartResult() {
+ return daemonStartResult;
+ }
+
+ public synchronized boolean isStartedOkay() {
+ return startedOkay;
+ }
+
+ private synchronized void setStartedOkay(boolean startedOkay) {
+ this.startedOkay = startedOkay;
+ }
+
+ public void stopDaemon() {
+ if(isStartedOkay()){
+ try {I2PD_JNI.stopDaemon();}catch(Throwable tr){Log.e(TAG, "", tr);}
+ setStartedOkay(false);
+ }
+ }
+}
diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java
index ef22f941..5d8322ed 100755
--- a/android/src/org/purplei2p/i2pd/I2PD.java
+++ b/android/src/org/purplei2p/i2pd/I2PD.java
@@ -5,6 +5,8 @@ import java.io.StringWriter;
import java.util.Timer;
import java.util.TimerTask;
+import org.purplei2p.i2pd.DaemonSingleton.State;
+
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentName;
@@ -22,52 +24,47 @@ import android.widget.Toast;
public class I2PD extends Activity {
private static final String TAG = "i2pd";
-
- private static Throwable loadLibsThrowable;
- static {
- try {
- I2PD_JNI.loadLibraries();
- } catch (Throwable tr) {
- loadLibsThrowable = tr;
+ private DaemonSingleton daemon = DaemonSingleton.getInstance();
+ private DaemonSingleton.StateChangeListener daemonStateChangeListener =
+ new DaemonSingleton.StateChangeListener() {
+
+ @Override
+ public void daemonStateChanged() {
+ runOnUiThread(new Runnable(){
+
+ @Override
+ public void run() {
+ try {
+ if(textView==null)return;
+ Throwable tr = daemon.getLastThrowable();
+ if(tr!=null) {
+ textView.setText(throwableToString(tr));
+ return;
+ }
+ DaemonSingleton.State state = daemon.getState();
+ textView.setText(String.valueOf(state)+
+ (DaemonSingleton.State.startFailed.equals(state)?": "+daemon.getDaemonStartResult():""));
+ } catch (Throwable tr) {
+ Log.e(TAG,"error ignored",tr);
+ }
+ }
+ });
}
- }
- private String daemonStartResult="N/A";
- private boolean destroyed=false;
+ };
+
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- destroyed=false;
//set the app be foreground (do not unload when RAM needed)
doBindService();
textView = new TextView(this);
setContentView(textView);
- if (loadLibsThrowable != null) {
- textView.setText(throwableToString(loadLibsThrowable)+"\r\n");
- return;
- }
- try {
- textView.setText(
- "libi2pd.so was compiled with ABI " + getABICompiledWith() + "\r\n"+
- "Starting daemon... ");
- new Thread(new Runnable(){
-
- @Override
- public void run() {
- try {
- doStartDaemon();
- } catch (final Throwable tr) {
- appendThrowable(tr);
- }
- }
-
- },"i2pdDaemonStarting").start();
- } catch (Throwable tr) {
- textView.setText(textView.getText().toString()+throwableToString(tr));
- }
+ daemonStateChangeListener.daemonStateChanged();
+ daemon.addStateChangeListener(daemonStateChangeListener);
}
@Override
@@ -76,25 +73,18 @@ public class I2PD extends Activity {
localDestroy();
}
- private synchronized void localDestroy() {
- if(destroyed)return;
- destroyed=true;
- if(gracefulQuitTimer!=null) {
- gracefulQuitTimer.cancel();
- gracefulQuitTimer = null;
+ private void localDestroy() {
+ textView = null;
+ daemon.removeStateChangeListener(daemonStateChangeListener);
+ if(getGracefulQuitTimer()!=null) {
+ getGracefulQuitTimer().cancel();
+ setGracefulQuitTimer(null);
}
try{
doUnbindService();
}catch(Throwable tr){
Log.e(TAG, "", tr);
}
- if("ok".equals(daemonStartResult)) {
- try {
- I2PD_JNI.stopDaemon();
- } catch (Throwable tr) {
- Log.e(TAG, "error", tr);
- }
- }
}
private CharSequence throwableToString(Throwable tr) {
@@ -105,42 +95,6 @@ public class I2PD extends Activity {
return sw.toString();
}
- public String getABICompiledWith() {
- return I2PD_JNI.getABICompiledWith();
- }
-
- private synchronized void doStartDaemon() {
- if(destroyed)return;
- daemonStartResult = I2PD_JNI.startDaemon();
- runOnUiThread(new Runnable(){
-
- @Override
- public void run() {
- synchronized (I2PD.this) {
- if(destroyed)return;
- textView.setText(
- textView.getText().toString()+
- "start result: "+daemonStartResult+"\r\n"
- );
- }
- }
-
- });
- }
-
- private void appendThrowable(final Throwable tr) {
- runOnUiThread(new Runnable(){
-
- @Override
- public void run() {
- synchronized (I2PD.this) {
- if(destroyed)return;
- textView.setText(textView.getText().toString()+throwableToString(tr)+"\r\n");
- }
- }
- });
- }
-
// private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
@@ -218,7 +172,6 @@ public class I2PD extends Activity {
@SuppressLint("NewApi")
private void quit() {
try {
- localDestroy();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
@@ -230,11 +183,18 @@ public class I2PD extends Activity {
}catch (Throwable tr) {
Log.e(TAG, "", tr);
}
+ try{
+ daemon.stopDaemon();
+ }catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ System.exit(0);
}
- private Timer gracefulQuitTimer;
- private synchronized void gracefulQuit() {
- if(gracefulQuitTimer!=null){
+ private Timer gracefulQuitTimer;
+ private final Object gracefulQuitTimerLock = new Object();
+ private void gracefulQuit() {
+ if(getGracefulQuitTimer()!=null){
Toast.makeText(this, R.string.graceful_quit_is_already_in_progress,
Toast.LENGTH_SHORT).show();
return;
@@ -246,22 +206,21 @@ public class I2PD extends Activity {
@Override
public void run() {
try{
- synchronized (I2PD.this) {
- if("ok".equals(daemonStartResult)) {
- I2PD_JNI.stopAcceptingTunnels();
- gracefulQuitTimer = new Timer(true);
- gracefulQuitTimer.schedule(new TimerTask(){
-
- @Override
- public void run() {
- quit();
- }
-
- }, 10*60*1000/*millis*/);
- }else{
- quit();
- }
- }
+ Log.d(TAG, "grac stopping");
+ if(daemon.isStartedOkay()) {
+ daemon.stopAcceptingTunnels();
+ setGracefulQuitTimer(new Timer(true));
+ getGracefulQuitTimer().schedule(new TimerTask(){
+
+ @Override
+ public void run() {
+ quit();
+ }
+
+ }, 10*60*1000/*milliseconds*/);
+ }else{
+ quit();
+ }
} catch(Throwable tr) {
Log.e(TAG,"",tr);
}
@@ -269,4 +228,16 @@ public class I2PD extends Activity {
},"gracQuitInit").start();
}
+
+ private Timer getGracefulQuitTimer() {
+ synchronized (gracefulQuitTimerLock) {
+ return gracefulQuitTimer;
+ }
+ }
+
+ private void setGracefulQuitTimer(Timer gracefulQuitTimer) {
+ synchronized (gracefulQuitTimerLock) {
+ this.gracefulQuitTimer = gracefulQuitTimer;
+ }
+ }
}