diff --git a/android/.gitignore b/android/.gitignore
index d9fa5a57..90cd315e 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -5,4 +5,11 @@ ant.properties
local.properties
build.sh
bin
-log*
\ No newline at end of file
+log*
+.gradle
+android.iml
+build
+gradle
+gradlew
+gradlew.bat
+
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index c147a808..1421b261 100755
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -1,12 +1,17 @@
i2pd
- i2pd started
- i2pd service started
- i2pd service stopped
Stop
Graceful Stop
Graceful stop is already in progress
Graceful stop is in progress
Already stopped
+ i2pd initializing
+ i2pd is starting
+ i2pd: loaded JNI libraries
+ i2pd started
+ i2pd start failed
+ i2pd: graceful shutdown in progress
+ i2pd has stopped
+ remaining
diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java
index 64568f83..4f3e62f7 100644
--- a/android/src/org/purplei2p/i2pd/DaemonSingleton.java
+++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java
@@ -8,8 +8,8 @@ import android.util.Log;
public class DaemonSingleton {
private static final String TAG="i2pd";
private static final DaemonSingleton instance = new DaemonSingleton();
- public static interface StateUpdateListener { void daemonStateUpdate(); }
- private final Set stateUpdateListeners = new HashSet();
+ public interface StateUpdateListener { void daemonStateUpdate(); }
+ private final Set stateUpdateListeners = new HashSet<>();
public static DaemonSingleton getInstance() {
return instance;
@@ -18,63 +18,72 @@ public class DaemonSingleton {
public synchronized void addStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.add(listener); }
public synchronized void removeStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.remove(listener); }
+ private synchronized void setState(State newState) {
+ if(newState==null)throw new NullPointerException();
+ State oldState = state;
+ if(oldState==null)throw new NullPointerException();
+ if(oldState.equals(newState))return;
+ state=newState;
+ fireStateUpdate1();
+ }
public synchronized void stopAcceptingTunnels() {
if(isStartedOkay()){
- state=State.gracefulShutdownInProgress;
- fireStateUpdate();
+ setState(State.gracefulShutdownInProgress);
I2PD_JNI.stopAcceptingTunnels();
}
}
- public void onNetworkStateChange(boolean isConnected) {
- I2PD_JNI.onNetworkStateChanged(isConnected);
- }
+ private volatile boolean startedOkay;
- private boolean startedOkay;
+ public enum State {
+ uninitialized(R.string.uninitialized),
+ starting(R.string.starting),
+ jniLibraryLoaded(R.string.jniLibraryLoaded),
+ startedOkay(R.string.startedOkay),
+ startFailed(R.string.startFailed),
+ gracefulShutdownInProgress(R.string.gracefulShutdownInProgress),
+ stopped(R.string.stopped);
- public static enum State {uninitialized,starting,jniLibraryLoaded,startedOkay,startFailed,gracefulShutdownInProgress,stopped};
+ State(int statusStringResourceId) {
+ this.statusStringResourceId = statusStringResourceId;
+ }
- private State state = State.uninitialized;
+ private final int statusStringResourceId;
+
+ public int getStatusStringResourceId() {
+ return statusStringResourceId;
+ }
+ };
+
+ private volatile State state = State.uninitialized;
public State getState() { return state; }
- public synchronized void start() {
- if(state != State.uninitialized)return;
- state = State.starting;
- fireStateUpdate();
+ {
+ setState(State.starting);
new Thread(new Runnable(){
@Override
public void run() {
try {
I2PD_JNI.loadLibraries();
- synchronized (DaemonSingleton.this) {
- state = State.jniLibraryLoaded;
- fireStateUpdate();
- }
+ setState(State.jniLibraryLoaded);
} catch (Throwable tr) {
lastThrowable=tr;
- synchronized (DaemonSingleton.this) {
- state = State.startFailed;
- fireStateUpdate();
- }
+ setState(State.startFailed);
return;
}
try {
synchronized (DaemonSingleton.this) {
daemonStartResult = I2PD_JNI.startDaemon();
if("ok".equals(daemonStartResult)){
- state=State.startedOkay;
+ setState(State.startedOkay);
setStartedOkay(true);
- }else state=State.startFailed;
- fireStateUpdate();
+ }else setState(State.startFailed);
}
} catch (Throwable tr) {
lastThrowable=tr;
- synchronized (DaemonSingleton.this) {
- state = State.startFailed;
- fireStateUpdate();
- }
+ setState(State.startFailed);
return;
}
}
@@ -84,7 +93,7 @@ public class DaemonSingleton {
private Throwable lastThrowable;
private String daemonStartResult="N/A";
- private synchronized void fireStateUpdate() {
+ private void fireStateUpdate1() {
Log.i(TAG, "daemon state change: "+state);
for(StateUpdateListener listener : stateUpdateListeners) {
try {
@@ -121,10 +130,7 @@ public class DaemonSingleton {
if(isStartedOkay()){
try {I2PD_JNI.stopDaemon();}catch(Throwable tr){Log.e(TAG, "", tr);}
setStartedOkay(false);
- synchronized (DaemonSingleton.this) {
- state = State.stopped;
- fireStateUpdate();
- }
+ setState(State.stopped);
}
}
}
diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java
index 74761b07..6116b982 100644
--- a/android/src/org/purplei2p/i2pd/ForegroundService.java
+++ b/android/src/org/purplei2p/i2pd/ForegroundService.java
@@ -11,11 +11,32 @@ import android.util.Log;
import android.widget.Toast;
public class ForegroundService extends Service {
+ private static final String TAG="FgService";
+
+ private volatile boolean shown;
+
+ private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener =
+ new DaemonSingleton.StateUpdateListener() {
+
+ @Override
+ public void daemonStateUpdate() {
+ try {
+ synchronized (ForegroundService.this) {
+ if (shown) cancelNotification();
+ showNotification();
+ }
+ } catch (Throwable tr) {
+ Log.e(TAG,"error ignored",tr);
+ }
+ }
+ };
+
+
private NotificationManager notificationManager;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
- private int NOTIFICATION = R.string.i2pd_started;
+ private int NOTIFICATION = 1;
/**
* Class for clients to access. Because we know this service always
@@ -32,29 +53,35 @@ public class ForegroundService extends Service {
public void onCreate() {
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
- // Display a notification about us starting. We put an icon in the status bar.
- showNotification();
- daemon.start();
+ synchronized (this) {
+ DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener);
+ if (!shown) daemonStateUpdatedListener.daemonStateUpdate();
+ }
// Tell the user we started.
- Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show();
+// Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("ForegroundService", "Received start id " + startId + ": " + intent);
- daemon.start();
return START_STICKY;
}
@Override
public void onDestroy() {
+ DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener);
+ cancelNotification();
+ }
+
+ private synchronized void cancelNotification() {
// Cancel the persistent notification.
notificationManager.cancel(NOTIFICATION);
stopForeground(true);
// Tell the user we stopped.
- Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show();
+// Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show();
+ shown=false;
}
@Override
@@ -69,9 +96,9 @@ public class ForegroundService extends Service {
/**
* Show a notification while this service is running.
*/
- private void showNotification() {
+ private synchronized void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
- CharSequence text = getText(R.string.i2pd_started);
+ CharSequence text = getText(DaemonSingleton.getInstance().getState().getStatusStringResourceId());
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
@@ -90,8 +117,9 @@ public class ForegroundService extends Service {
// Send the notification.
//mNM.notify(NOTIFICATION, notification);
startForeground(NOTIFICATION, notification);
+ shown=true;
}
- private final DaemonSingleton daemon = DaemonSingleton.getInstance();
+ private static final DaemonSingleton daemon = DaemonSingleton.getInstance();
}
diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java
index 36e992b3..99672eb7 100755
--- a/android/src/org/purplei2p/i2pd/I2PDActivity.java
+++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java
@@ -19,13 +19,14 @@ import android.widget.TextView;
import android.widget.Toast;
public class I2PDActivity extends Activity {
- private static final String TAG = "i2pd";
+ private static final String TAG = "i2pdActvt";
+ public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000;
- private TextView textView;
+ private TextView textView;
- private final DaemonSingleton daemon = DaemonSingleton.getInstance();
+ private static final DaemonSingleton daemon = DaemonSingleton.getInstance();
- private DaemonSingleton.StateUpdateListener daemonStateUpdatedListener =
+ private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener =
new DaemonSingleton.StateUpdateListener() {
@Override
@@ -42,8 +43,11 @@ public class I2PDActivity extends Activity {
return;
}
DaemonSingleton.State state = daemon.getState();
- textView.setText(String.valueOf(state)+
- (DaemonSingleton.State.startFailed.equals(state)?": "+daemon.getDaemonStartResult():""));
+ textView.setText(
+ String.valueOf(state)+
+ (DaemonSingleton.State.startFailed.equals(state)?": "+daemon.getDaemonStartResult():"")+
+ (DaemonSingleton.State.gracefulShutdownInProgress.equals(state)?": "+formatGraceTimeRemaining()+" "+getText(R.string.remaining):"")
+ );
} catch (Throwable tr) {
Log.e(TAG,"error ignored",tr);
}
@@ -51,6 +55,18 @@ public class I2PDActivity extends Activity {
});
}
};
+ private static volatile long graceStartedMillis;
+ private static final Object graceStartedMillis_LOCK=new Object();
+
+ private static String formatGraceTimeRemaining() {
+ long remainingSeconds;
+ synchronized (graceStartedMillis_LOCK){
+ remainingSeconds=Math.round(Math.max(0,graceStartedMillis+GRACEFUL_DELAY_MILLIS-System.currentTimeMillis())/1000.0D);
+ }
+ long remainingMinutes=(long)Math.floor(remainingSeconds/60.0D);
+ long remSec=remainingSeconds-remainingMinutes*60;
+ return remainingMinutes+":"+(remSec/10)+remSec%10;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -58,35 +74,44 @@ public class I2PDActivity extends Activity {
textView = new TextView(this);
setContentView(textView);
- DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener);
+ daemon.addStateChangeListener(daemonStateUpdatedListener);
daemonStateUpdatedListener.daemonStateUpdate();
//set the app be foreground
doBindService();
+
+ final Timer gracefulQuitTimer = getGracefulQuitTimer();
+ if(gracefulQuitTimer!=null){
+ long gracefulStopAtMillis;
+ synchronized (graceStartedMillis_LOCK) {
+ gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS;
+ }
+ rescheduleGraceStop(gracefulQuitTimer, gracefulStopAtMillis);
+ }
}
@Override
protected void onDestroy() {
super.onDestroy();
- localDestroy();
- }
-
- private void localDestroy() {
- textView = null;
- DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener);
- Timer gracefulQuitTimer = getGracefulQuitTimer();
- if(gracefulQuitTimer!=null) {
- gracefulQuitTimer.cancel();
- setGracefulQuitTimer(null);
+ textView = null;
+ daemon.removeStateChangeListener(daemonStateUpdatedListener);
+ //cancelGracefulStop();
+ try{
+ doUnbindService();
+ }catch(Throwable tr){
+ Log.e(TAG, "", tr);
}
-// try{
-// doUnbindService();
-// }catch(Throwable tr){
-// Log.e(TAG, "", tr);
-// }
}
- private CharSequence throwableToString(Throwable tr) {
+ private static void cancelGracefulStop() {
+ Timer gracefulQuitTimer = getGracefulQuitTimer();
+ if(gracefulQuitTimer!=null) {
+ gracefulQuitTimer.cancel();
+ setGracefulQuitTimer(null);
+ }
+ }
+
+ private CharSequence throwableToString(Throwable tr) {
StringWriter sw = new StringWriter(8192);
PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
@@ -122,24 +147,27 @@ public class I2PDActivity extends Activity {
};
- private boolean mIsBound;
+ private static volatile boolean mIsBound;
- 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 void doBindService() {
+ synchronized (I2PDActivity.class) {
+ 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 void doUnbindService() {
- if (mIsBound) {
- // Detach our existing connection.
- unbindService(mConnection);
- mIsBound = false;
+ synchronized (I2PDActivity.class) {
+ if (mIsBound) {
+ // Detach our existing connection.
+ unbindService(mConnection);
+ mIsBound = false;
+ }
}
}
@@ -170,16 +198,25 @@ public class I2PDActivity extends Activity {
}
private void i2pdStop() {
- try{
- daemon.stopDaemon();
- }catch (Throwable tr) {
- Log.e(TAG, "", tr);
- }
+ cancelGracefulStop();
+ new Thread(new Runnable(){
+
+ @Override
+ public void run() {
+ Log.d(TAG, "stopping");
+ try{
+ daemon.stopDaemon();
+ }catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ }
+
+ },"stop").start();
}
- private Timer gracefulQuitTimer;
- private final Object gracefulQuitTimerLock = new Object();
- private synchronized void i2pdGracefulStop() {
+ private static volatile Timer gracefulQuitTimer;
+
+ private void i2pdGracefulStop() {
if(daemon.getState()==DaemonSingleton.State.stopped){
Toast.makeText(this, R.string.already_stopped,
Toast.LENGTH_SHORT).show();
@@ -200,16 +237,12 @@ public class I2PDActivity extends Activity {
Log.d(TAG, "grac stopping");
if(daemon.isStartedOkay()) {
daemon.stopAcceptingTunnels();
- Timer gracefulQuitTimer = new Timer(true);
- setGracefulQuitTimer(gracefulQuitTimer);
- gracefulQuitTimer.schedule(new TimerTask(){
-
- @Override
- public void run() {
- i2pdStop();
- }
-
- }, 10*60*1000/*milliseconds*/);
+ long gracefulStopAtMillis;
+ synchronized (graceStartedMillis_LOCK) {
+ graceStartedMillis = System.currentTimeMillis();
+ gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS;
+ }
+ rescheduleGraceStop(null,gracefulStopAtMillis);
}else{
i2pdStop();
}
@@ -218,18 +251,35 @@ public class I2PDActivity extends Activity {
}
}
- },"gracQuitInit").start();
+ },"gracInit").start();
}
- private Timer getGracefulQuitTimer() {
- synchronized (gracefulQuitTimerLock) {
- return gracefulQuitTimer;
- }
+ private void rescheduleGraceStop(Timer gracefulQuitTimerOld, long gracefulStopAtMillis) {
+ if(gracefulQuitTimerOld!=null)gracefulQuitTimerOld.cancel();
+ final Timer gracefulQuitTimer = new Timer(true);
+ setGracefulQuitTimer(gracefulQuitTimer);
+ gracefulQuitTimer.schedule(new TimerTask(){
+
+ @Override
+ public void run() {
+ i2pdStop();
+ }
+
+ }, Math.max(0,gracefulStopAtMillis-System.currentTimeMillis()));
+ final TimerTask tickerTask = new TimerTask() {
+ @Override
+ public void run() {
+ daemonStateUpdatedListener.daemonStateUpdate();
+ }
+ };
+ gracefulQuitTimer.scheduleAtFixedRate(tickerTask,0/*start delay*/,1000/*millis period*/);
+ }
+
+ private static Timer getGracefulQuitTimer() {
+ return gracefulQuitTimer;
}
- private void setGracefulQuitTimer(Timer gracefulQuitTimer) {
- synchronized (gracefulQuitTimerLock) {
- this.gracefulQuitTimer = gracefulQuitTimer;
- }
+ private static void setGracefulQuitTimer(Timer gracefulQuitTimer) {
+ I2PDActivity.gracefulQuitTimer = gracefulQuitTimer;
}
}