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; + } + } }