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() {