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 cba29a18..4f3e62f7 100644 --- a/android/src/org/purplei2p/i2pd/DaemonSingleton.java +++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java @@ -35,7 +35,25 @@ public class DaemonSingleton { private volatile boolean startedOkay; - public enum State {uninitialized,starting,jniLibraryLoaded,startedOkay,startFailed,gracefulShutdownInProgress,stopped}; + 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); + + State(int statusStringResourceId) { + this.statusStringResourceId = statusStringResourceId; + } + + private final int statusStringResourceId; + + public int getStatusStringResourceId() { + return statusStringResourceId; + } + }; private volatile State state = State.uninitialized; diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java index 07254439..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,8 +53,10 @@ 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(); + 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(); } @@ -46,6 +69,11 @@ public class ForegroundService extends Service { @Override public void onDestroy() { + DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); + cancelNotification(); + } + + private synchronized void cancelNotification() { // Cancel the persistent notification. notificationManager.cancel(NOTIFICATION); @@ -53,6 +81,7 @@ public class ForegroundService extends Service { // Tell the user we stopped. // Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); + shown=false; } @Override @@ -67,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, @@ -88,6 +117,7 @@ public class ForegroundService extends Service { // Send the notification. //mNM.notify(NOTIFICATION, notification); startForeground(NOTIFICATION, notification); + shown=true; } 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 64316112..af0c8000 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -19,9 +19,10 @@ 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 static final DaemonSingleton daemon = DaemonSingleton.getInstance(); @@ -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 volatile long graceStartedMillis; + private final Object graceStartedMillis_LOCK=new Object(); + + private 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) { @@ -70,11 +86,7 @@ public class I2PDActivity extends Activity { super.onDestroy(); textView = null; daemon.removeStateChangeListener(daemonStateUpdatedListener); - Timer gracefulQuitTimer = getGracefulQuitTimer(); - if(gracefulQuitTimer!=null) { - gracefulQuitTimer.cancel(); - setGracefulQuitTimer(null); - } + cancelGracefulStop(); try{ doUnbindService(); }catch(Throwable tr){ @@ -82,7 +94,15 @@ public class I2PDActivity extends Activity { } } - private CharSequence throwableToString(Throwable tr) { + private 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); @@ -169,11 +189,20 @@ 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 volatile Timer gracefulQuitTimer; @@ -199,8 +228,11 @@ public class I2PDActivity extends Activity { Log.d(TAG, "grac stopping"); if(daemon.isStartedOkay()) { daemon.stopAcceptingTunnels(); - Timer gracefulQuitTimer = new Timer(true); + final Timer gracefulQuitTimer = new Timer(true); setGracefulQuitTimer(gracefulQuitTimer); + synchronized (graceStartedMillis_LOCK) { + graceStartedMillis = System.currentTimeMillis(); + } gracefulQuitTimer.schedule(new TimerTask(){ @Override @@ -208,7 +240,14 @@ public class I2PDActivity extends Activity { i2pdStop(); } - }, 10*60*1000/*milliseconds*/); + }, GRACEFUL_DELAY_MILLIS); + final TimerTask tickerTask = new TimerTask() { + @Override + public void run() { + daemonStateUpdatedListener.daemonStateUpdate(); + } + }; + gracefulQuitTimer.scheduleAtFixedRate(tickerTask,0/*start delay*/,1000/*millis period*/); }else{ i2pdStop(); }