diff --git a/app/src/main/java/net/i2p/android/router/MainActivity.java b/app/src/main/java/net/i2p/android/router/MainActivity.java index 1af58541e..caf92e679 100644 --- a/app/src/main/java/net/i2p/android/router/MainActivity.java +++ b/app/src/main/java/net/i2p/android/router/MainActivity.java @@ -315,4 +315,32 @@ public class MainActivity extends I2PActivityBase implements } return false; } + + /** @since 0.9.18 */ + public boolean isGracefulShutdownInProgress() { + RouterService svc = _routerService; + return svc != null && svc.isGracefulShutdownInProgress(); + } + + /** @since 0.9.18 */ + public boolean onGracefulShutdownClicked() { + RouterService svc = _routerService; + if(svc != null && _isBound) { + setPref(PREF_AUTO_START, false); + svc.gracefulShutdown(); + return true; + } + return false; + } + + /** @since 0.9.18 */ + public boolean onCancelGracefulShutdownClicked() { + RouterService svc = _routerService; + if(svc != null && _isBound) { + setPref(PREF_AUTO_START, false); + svc.cancelGracefulShutdown(); + return true; + } + return false; + } } diff --git a/app/src/main/java/net/i2p/android/router/MainFragment.java b/app/src/main/java/net/i2p/android/router/MainFragment.java index 3ae533c94..5cf32411b 100644 --- a/app/src/main/java/net/i2p/android/router/MainFragment.java +++ b/app/src/main/java/net/i2p/android/router/MainFragment.java @@ -61,6 +61,12 @@ public class MainFragment extends I2PFragmentBase { public boolean shouldBeOn(); public void onStartRouterClicked(); public boolean onStopRouterClicked(); + /** @since 0.9.18 */ + public boolean isGracefulShutdownInProgress(); + /** @since 0.9.18 */ + public boolean onGracefulShutdownClicked(); + /** @since 0.9.18 */ + public boolean onCancelGracefulShutdownClicked(); } @Override @@ -118,8 +124,14 @@ public class MainFragment extends I2PFragmentBase { updateOneShot(); checkFirstStart(); } else { - if(mCallback.onStopRouterClicked()) { - updateOneShot(); + if (mCallback.isGracefulShutdownInProgress()) { + if(mCallback.onStopRouterClicked()) { + updateOneShot(); + } + } else { + if(mCallback.onGracefulShutdownClicked()) { + updateOneShot(); + } } } return true; @@ -197,6 +209,21 @@ public class MainFragment extends I2PFragmentBase { boolean isOn = mCallback.shouldBeOn(); b.setChecked(isOn); + if (isOn && mCallback.isGracefulShutdownInProgress()) { + RouterContext ctx = getRouterContext(); + if (ctx != null) { + // TODO + // Don't change text on this button... hide it, + // and add two more buttons, one for cancel and one for shutdown immediately. + long ms = ctx.router().getShutdownTimeRemaining(); + if (ms > 1000) { + b.setTextOn(getActivity().getResources().getString(R.string.button_router_graceful, + DataHelper.formatDuration(ms))); + } else { + b.setTextOn("Stopping I2P"); + } + } + } if (showOnOff && !isOn) { // Sometimes the final state message from the RouterService @@ -249,6 +276,7 @@ public class MainFragment extends I2PFragmentBase { newState == State.NETWORK_STOPPED) { lightImage.setImageResource(R.drawable.routerlogo_0); } else if (newState == State.STARTING || + newState == State.GRACEFUL_SHUTDOWN || newState == State.STOPPING || newState == State.MANUAL_STOPPING || newState == State.MANUAL_QUITTING || diff --git a/app/src/main/java/net/i2p/android/router/service/RouterService.java b/app/src/main/java/net/i2p/android/router/service/RouterService.java index 1757df2f2..3b267b988 100644 --- a/app/src/main/java/net/i2p/android/router/service/RouterService.java +++ b/app/src/main/java/net/i2p/android/router/service/RouterService.java @@ -87,7 +87,7 @@ public class RouterService extends Service { Intent intent = new Intent(this, RouterService.class); intent.putExtra(EXTRA_RESTART, true); onStartCommand(intent, 12345, 67890); - } else if(lastState == State.MANUAL_QUITTING) { + } else if(lastState == State.MANUAL_QUITTING || lastState == State.GRACEFUL_SHUTDOWN) { synchronized(_stateLock) { setState(State.MANUAL_QUITTED); stopSelf(); // Die. @@ -343,7 +343,7 @@ public class RouterService extends Service { public void run() { RouterContext ctx = _context; - if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE)) { + if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN)) { Router router = ctx.router(); if(router.isAlive()) { updateStatus(ctx); @@ -472,7 +472,8 @@ public class RouterService extends Service { && _state != State.STOPPING && _state != State.MANUAL_STOPPING && _state != State.MANUAL_QUITTING - && _state != State.NETWORK_STOPPING) { + && _state != State.NETWORK_STOPPING + && _state != State.GRACEFUL_SHUTDOWN) { return null; } return rv; @@ -486,11 +487,15 @@ public class RouterService extends Service { } public boolean canManualStop() { - return _state == State.WAITING || _state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE; + return _state == State.WAITING || _state == State.STARTING || + _state == State.RUNNING || _state == State.ACTIVE || + _state == State.GRACEFUL_SHUTDOWN; } /** * Stop and don't restart the router, but keep the service + * + * Apparently unused - see manualQuit() */ public void manualStop() { Util.d("manualStop called" @@ -502,7 +507,8 @@ public class RouterService extends Service { if(_state == State.STARTING) { _starterThread.interrupt(); } - if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) { + if(_state == State.STARTING || _state == State.RUNNING || + _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { _statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P"); Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED)); stopperThread.start(); @@ -523,7 +529,8 @@ public class RouterService extends Service { if(_state == State.STARTING) { _starterThread.interrupt(); } - if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) { + if(_state == State.STARTING || _state == State.RUNNING || + _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { _statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P"); Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED)); stopperThread.start(); @@ -544,7 +551,8 @@ public class RouterService extends Service { if(_state == State.STARTING) { _starterThread.interrupt(); } - if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) { + if(_state == State.STARTING || _state == State.RUNNING || + _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { _statusBar.replace(StatusBar.ICON_STOPPING, "Network disconnected, stopping I2P"); // don't change state, let the shutdown hook do it Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING)); @@ -572,6 +580,79 @@ public class RouterService extends Service { } } + /** + * Graceful Shutdown + * + * @since 0.9.18 + */ + public boolean isGracefulShutdownInProgress() { + if (_state == State.GRACEFUL_SHUTDOWN) { + RouterContext ctx = _context; + return ctx != null && ctx.router().gracefulShutdownInProgress(); + } + return false; + } + + /** + * Graceful Shutdown + * + * @since 0.9.18 + */ + public void gracefulShutdown() { + Util.d("gracefulShutdown called" + + " Current state is: " + _state); + synchronized(_stateLock) { + if(!canManualStop()) { + return; + } + if(_state == State.STARTING || _state == State.WAITING) { + manualQuit(); + return; + } + if(_state == State.RUNNING || _state == State.ACTIVE) { + RouterContext ctx = _context; + if(ctx != null && ctx.router().isAlive()) { + int part = ctx.tunnelManager().getParticipatingCount(); + if(part <= 0) { + manualQuit(); + } else { + ctx.router().shutdownGracefully(); + long ms = ctx.router().getShutdownTimeRemaining(); + if (ms > 1000) { + _statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P in " + DataHelper.formatDuration(ms)); + } else { + _statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P"); + } + setState(State.GRACEFUL_SHUTDOWN); + } + } + } + } + } + + /** + * Cancel Graceful Shutdown + * + * @since 0.9.18 + */ + public void cancelGracefulShutdown() { + Util.d("cancelGracefulShutdown called" + + " Current state is: " + _state); + synchronized(_stateLock) { + if(_state != State.GRACEFUL_SHUTDOWN) { + return; + } + RouterContext ctx = _context; + if(ctx != null && ctx.router().isAlive()) { + ctx.router().cancelGracefulShutdown(); + _statusBar.replace(StatusBar.ICON_RUNNING, "Shutdown cancelled"); + setState(State.RUNNING); + } + } + } + + + // ******** end methods accessed from Activities and Receivers ************ private static final int STATE_MSG = 1; @@ -631,7 +712,8 @@ public class RouterService extends Service { if(_state == State.STARTING) { _starterThread.interrupt(); } - if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) { + if(_state == State.STARTING || _state == State.RUNNING || + _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { // should this be in a thread? _statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down"); Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED)); @@ -708,7 +790,8 @@ public class RouterService extends Service { _starterThread.interrupt(); } if(_state == State.WAITING || _state == State.STARTING - || _state == State.RUNNING || _state == State.ACTIVE) { + || _state == State.RUNNING || _state == State.ACTIVE + || _state == State.GRACEFUL_SHUTDOWN) { setState(State.STOPPING); } } @@ -750,7 +833,7 @@ public class RouterService extends Service { mStateCallbacks.kill(); stopForeground(true); stopSelf(); - } else if(_state == State.MANUAL_QUITTING) { + } else if(_state == State.MANUAL_QUITTING || _state == State.GRACEFUL_SHUTDOWN) { setState(State.MANUAL_QUITTED); // Unregister all callbacks. mStateCallbacks.kill(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6be462df3..af22da386 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ Long press to start I2P I2P is running (long press to stop) + I2P is shutting down in %s (long press to stop immediately) C @@ -227,4 +228,4 @@ Logs copied to clipboard Browser configuration - \ No newline at end of file + diff --git a/client/src/main/java/net/i2p/android/router/service/State.java b/client/src/main/java/net/i2p/android/router/service/State.java index 383c8f095..0feb6bd0b 100644 --- a/client/src/main/java/net/i2p/android/router/service/State.java +++ b/client/src/main/java/net/i2p/android/router/service/State.java @@ -19,7 +19,9 @@ public enum State implements Parcelable { // button, DO kill service when stopped, next: killSelf() MANUAL_QUITTING, MANUAL_QUITTED, // Stopped by listener (no network), next: WAITING (spin waiting for network) - NETWORK_STOPPING, NETWORK_STOPPED; + NETWORK_STOPPING, NETWORK_STOPPED, + /** @since 0.9.18 */ + GRACEFUL_SHUTDOWN; @Override public int describeContents() {