703 lines
25 KiB
Java
703 lines
25 KiB
Java
package net.i2p.android.router.service;
|
|
|
|
import android.app.Service;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.text.DecimalFormat;
|
|
import java.util.List;
|
|
import java.util.Properties;
|
|
import java.util.Random;
|
|
import net.i2p.android.router.binder.RouterBinder;
|
|
import net.i2p.android.router.receiver.I2PReceiver;
|
|
import net.i2p.android.router.util.Util;
|
|
import net.i2p.data.DataHelper;
|
|
import net.i2p.router.Job;
|
|
import net.i2p.router.Router;
|
|
import net.i2p.router.RouterContext;
|
|
import net.i2p.router.RouterLaunch;
|
|
import net.i2p.util.OrderedProperties;
|
|
|
|
/**
|
|
* Runs the router
|
|
*/
|
|
public class RouterService extends Service {
|
|
|
|
// These states persist even if we died... Yuck, it causes issues.
|
|
public enum State {
|
|
INIT, WAITING, STARTING, RUNNING,
|
|
// unplanned (router stopped itself), next: killSelf()
|
|
STOPPING, STOPPED,
|
|
// button, don't kill service when stopped, stay in MANUAL_STOPPED
|
|
MANUAL_STOPPING, MANUAL_STOPPED,
|
|
// 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
|
|
}
|
|
private RouterContext _context;
|
|
private String _myDir;
|
|
//private String _apkPath;
|
|
private State _state = State.INIT;
|
|
private Thread _starterThread;
|
|
private StatusBar _statusBar;
|
|
private I2PReceiver _receiver;
|
|
private IBinder _binder;
|
|
private final Object _stateLock = new Object();
|
|
private Handler _handler;
|
|
private Runnable _updater;
|
|
private static final String SHARED_PREFS = "net.i2p.android.router";
|
|
private static final String LAST_STATE = "service.lastState";
|
|
private static final String EXTRA_RESTART = "restart";
|
|
private static final String MARKER = "************************************** ";
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
State lastState = getSavedState();
|
|
setState(State.INIT);
|
|
Util.i(this + " onCreate called"
|
|
+ " Saved state is: " + lastState
|
|
+ " Current state is: " + _state);
|
|
|
|
//(new File(getFilesDir(), "wrapper.log")).delete();
|
|
_myDir = getFilesDir().getAbsolutePath();
|
|
// init other stuff here, delete log, etc.
|
|
Init init = new Init(this);
|
|
init.initialize();
|
|
//_apkPath = init.getAPKPath();
|
|
_statusBar = new StatusBar(this);
|
|
// Remove stale notification icon.
|
|
_statusBar.remove();
|
|
_binder = new RouterBinder(this);
|
|
_handler = new Handler();
|
|
_updater = new Updater();
|
|
if(lastState == State.RUNNING) {
|
|
Intent intent = new Intent(this, RouterService.class);
|
|
intent.putExtra(EXTRA_RESTART, true);
|
|
onStartCommand(intent, 12345, 67890);
|
|
} else if(lastState == State.MANUAL_QUITTING) {
|
|
synchronized(_stateLock) {
|
|
setState(State.MANUAL_QUITTED);
|
|
stopSelf(); // Die.
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* NOT called by system if it restarts us after a crash
|
|
*/
|
|
@Override
|
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
Util.i(this + " onStart called"
|
|
+ " Intent is: " + intent
|
|
+ " Flags is: " + flags
|
|
+ " ID is: " + startId
|
|
+ " Current state is: " + _state);
|
|
boolean restart = intent != null && intent.getBooleanExtra(EXTRA_RESTART, false);
|
|
if(restart) {
|
|
Util.i(this + " RESTARTING");
|
|
}
|
|
synchronized(_stateLock) {
|
|
if(_state != State.INIT) //return START_STICKY;
|
|
{
|
|
return START_NOT_STICKY;
|
|
}
|
|
_receiver = new I2PReceiver(this);
|
|
if(Util.isConnected(this)) {
|
|
if(restart) {
|
|
_statusBar.replace(StatusBar.ICON1, "I2P is restarting");
|
|
} else {
|
|
_statusBar.replace(StatusBar.ICON1, "I2P is starting up");
|
|
}
|
|
setState(State.STARTING);
|
|
_starterThread = new Thread(new Starter());
|
|
_starterThread.start();
|
|
} else {
|
|
_statusBar.replace(StatusBar.ICON6, "I2P is waiting for a network connection");
|
|
setState(State.WAITING);
|
|
_handler.postDelayed(new Waiter(), 10 * 1000);
|
|
}
|
|
}
|
|
_handler.removeCallbacks(_updater);
|
|
_handler.postDelayed(_updater, 50);
|
|
if(!restart) {
|
|
startForeground(1337, _statusBar.getNote());
|
|
}
|
|
|
|
//return START_STICKY;
|
|
return START_NOT_STICKY;
|
|
}
|
|
|
|
/**
|
|
* maybe this goes away when the receiver can bind to us
|
|
*/
|
|
private class Waiter implements Runnable {
|
|
|
|
public void run() {
|
|
Util.i(MARKER + this + " waiter handler"
|
|
+ " Current state is: " + _state);
|
|
if(_state == State.WAITING) {
|
|
if(Util.isConnected(RouterService.this)) {
|
|
synchronized(_stateLock) {
|
|
if(_state != State.WAITING) {
|
|
return;
|
|
}
|
|
_statusBar.replace(StatusBar.ICON1, "Network connected, I2P is starting up");
|
|
setState(State.STARTING);
|
|
_starterThread = new Thread(new Starter());
|
|
_starterThread.start();
|
|
}
|
|
return;
|
|
}
|
|
_handler.postDelayed(this, 15 * 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
private class Starter implements Runnable {
|
|
|
|
public void run() {
|
|
Util.i(MARKER + this + " starter thread"
|
|
+ " Current state is: " + _state);
|
|
//Util.i(MARKER + this + " JBigI speed test started");
|
|
//NativeBigInteger.main(null);
|
|
//Util.i(MARKER + this + " JBigI speed test finished, launching router");
|
|
|
|
|
|
// Before we launch, fix up any settings that need to be fixed here.
|
|
// This should be done in the core, but as of this writing it isn't!
|
|
|
|
// Step one. Load the propertites.
|
|
Properties props = new OrderedProperties();
|
|
Properties oldprops = new OrderedProperties();
|
|
String wrapName = _myDir + "/router.config";
|
|
try {
|
|
InputStream fin = new FileInputStream(new File(wrapName));
|
|
DataHelper.loadProps(props, fin);
|
|
} catch(IOException ioe) {
|
|
// shouldn't happen...
|
|
}
|
|
oldprops.putAll(props);
|
|
// Step two, check for any port settings, and copy for those that are missing.
|
|
int UDPinbound;
|
|
int UDPinlocal;
|
|
int TCPinbound;
|
|
int TCPinlocal;
|
|
UDPinbound = Integer.parseInt(props.getProperty("i2np.udp.port", "-1"));
|
|
UDPinlocal = Integer.parseInt(props.getProperty("i2np.udp.internalPort", "-1"));
|
|
TCPinbound = Integer.parseInt(props.getProperty("i2np.ntcp.port", "-1"));
|
|
TCPinlocal = Integer.parseInt(props.getProperty("i2np.ntcp.internalPort", "-1"));
|
|
boolean hasUDPinbound = UDPinbound != -1;
|
|
boolean hasUDPinlocal = UDPinlocal != -1;
|
|
boolean hasTCPinbound = TCPinbound != -1;
|
|
boolean hasTCPinlocal = TCPinlocal != -1;
|
|
|
|
// check and clear values based on these:
|
|
boolean udp = Boolean.parseBoolean(props.getProperty("i2np.udp.enable", "false"));
|
|
boolean tcp = Boolean.parseBoolean(props.getProperty("i2np.ntcp.enable", "false"));
|
|
|
|
// Fix if both are false.
|
|
if(!(udp || tcp)) {
|
|
// If both are not on, turn them both on.
|
|
props.setProperty("i2np.udp.enable", "true");
|
|
props.setProperty("i2np.ntcp.enable", "true");
|
|
}
|
|
|
|
// Fix if we have local but no inbound
|
|
if(!hasUDPinbound && hasUDPinlocal) {
|
|
// if we got a local port and no external port, set it
|
|
hasUDPinbound = true;
|
|
UDPinbound = UDPinlocal;
|
|
}
|
|
if(!hasTCPinbound && hasTCPinlocal) {
|
|
// if we got a local port and no external port, set it
|
|
hasTCPinbound = true;
|
|
TCPinbound = TCPinlocal;
|
|
}
|
|
|
|
boolean anyUDP = hasUDPinbound || hasUDPinlocal;
|
|
boolean anyTCP = hasTCPinbound || hasTCPinlocal;
|
|
boolean anyport = anyUDP || anyTCP;
|
|
|
|
if(!anyport) {
|
|
// generate one for UDPinbound, and fall thru.
|
|
// FIX ME: Possibly not the best but should be OK.
|
|
Random generator = new Random(System.currentTimeMillis());
|
|
UDPinbound = generator.nextInt(55500) + 10000;
|
|
anyUDP = true;
|
|
}
|
|
|
|
// Copy missing port numbers
|
|
if(anyUDP && !anyTCP) {
|
|
TCPinbound = UDPinbound;
|
|
TCPinlocal = UDPinlocal;
|
|
}
|
|
if(anyTCP && !anyUDP) {
|
|
UDPinbound = TCPinbound;
|
|
UDPinlocal = TCPinlocal;
|
|
}
|
|
// reset for a retest.
|
|
hasUDPinbound = UDPinbound != -1;
|
|
hasUDPinlocal = UDPinlocal != -1;
|
|
hasTCPinbound = TCPinbound != -1;
|
|
hasTCPinlocal = TCPinlocal != -1;
|
|
anyUDP = hasUDPinbound || hasUDPinlocal;
|
|
anyTCP = hasTCPinbound || hasTCPinlocal;
|
|
boolean checkAnyUDP = anyUDP && udp;
|
|
boolean checkAnyTCP = anyTCP && tcp;
|
|
|
|
// Enable things that need to be enabled.
|
|
// Disable anything that needs to be disabled.
|
|
if(!checkAnyUDP && !checkAnyTCP) {
|
|
// enable the one(s) with values.
|
|
if(anyUDP) {
|
|
udp = true;
|
|
}
|
|
if(anyTCP) {
|
|
tcp = true;
|
|
}
|
|
}
|
|
|
|
if(!udp) {
|
|
props.setProperty("i2np.udp.enable", "false");
|
|
props.remove("i2np.udp.port");
|
|
props.remove("i2np.udp.internalPort");
|
|
} else {
|
|
props.setProperty("i2np.udp.enable", "true");
|
|
if(hasUDPinbound) {
|
|
props.setProperty("i2np.udp.port", Integer.toString(UDPinbound));
|
|
} else {
|
|
props.remove("i2np.udp.port");
|
|
}
|
|
if(hasUDPinlocal) {
|
|
props.setProperty("i2np.udp.internalPort", Integer.toString(UDPinlocal));
|
|
} else {
|
|
props.remove("i2np.udp.internalPort");
|
|
}
|
|
}
|
|
|
|
if(!tcp) {
|
|
props.setProperty("i2np.ntcp.enable", "false");
|
|
props.remove("i2np.ntcp.port");
|
|
props.remove("i2np.ntcp.internalPort");
|
|
} else {
|
|
props.setProperty("i2np.ntcp.enable", "true");
|
|
if(hasTCPinbound) {
|
|
props.setProperty("i2np.ntcp.port", Integer.toString(TCPinbound));
|
|
} else {
|
|
props.remove("i2np.ntcp.port");
|
|
}
|
|
if(hasTCPinlocal) {
|
|
props.setProperty("i2np.ntcp.internalPort", Integer.toString(TCPinlocal));
|
|
} else {
|
|
props.remove("i2np.ntcp.internalPort");
|
|
}
|
|
}
|
|
// WHEW! Now test for any changes.
|
|
if(!props.equals(oldprops)) {
|
|
// save fixed properties.
|
|
try {
|
|
DataHelper.storeProps(props, new File(wrapName));
|
|
} catch(IOException ioe) {
|
|
// shouldn't happen...
|
|
}
|
|
}
|
|
|
|
// _NOW_ launch the router!
|
|
RouterLaunch.main(null);
|
|
synchronized(_stateLock) {
|
|
if(_state != State.STARTING) {
|
|
return;
|
|
}
|
|
setState(State.RUNNING);
|
|
List<?> contexts = RouterContext.listContexts();
|
|
if((contexts == null) || (contexts.isEmpty())) {
|
|
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
|
}
|
|
_statusBar.replace(StatusBar.ICON2, "I2P is running");
|
|
_context = (RouterContext) contexts.get(0);
|
|
_context.router().setKillVMOnEnd(false);
|
|
Job loadJob = new LoadClientsJob(_context);
|
|
_context.jobQueue().addJob(loadJob);
|
|
_context.addShutdownTask(new ShutdownHook());
|
|
_context.addFinalShutdownTask(new FinalShutdownHook());
|
|
_starterThread = null;
|
|
}
|
|
Util.i("Router.main finished");
|
|
}
|
|
}
|
|
|
|
private class Updater implements Runnable {
|
|
|
|
public void run() {
|
|
RouterContext ctx = _context;
|
|
if(ctx != null && _state == State.RUNNING) {
|
|
Router router = ctx.router();
|
|
if(router.isAlive()) {
|
|
updateStatus(ctx);
|
|
}
|
|
}
|
|
_handler.postDelayed(this, 15 * 1000);
|
|
}
|
|
}
|
|
private boolean _hadTunnels;
|
|
|
|
private void updateStatus(RouterContext ctx) {
|
|
int active = ctx.commSystem().countActivePeers();
|
|
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
|
int inEx = ctx.tunnelManager().getFreeTunnelCount();
|
|
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
|
|
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
|
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
|
|
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
|
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
|
|
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
|
|
// control total width
|
|
DecimalFormat fmt;
|
|
if(inBW >= 1000 || outBW >= 1000) {
|
|
fmt = new DecimalFormat("#0");
|
|
} else if(inBW >= 100 || outBW >= 100) {
|
|
fmt = new DecimalFormat("#0.0");
|
|
} else {
|
|
fmt = new DecimalFormat("#0.00");
|
|
}
|
|
|
|
String status =
|
|
"I2P "
|
|
+ active + '/' + known + " peers connected";
|
|
|
|
String details =
|
|
fmt.format(inBW) + '/' + fmt.format(outBW) + " KBps"
|
|
+ "; Expl " + inEx + '/' + outEx
|
|
+ "; Client " + inCl + '/' + outCl;
|
|
|
|
boolean haveTunnels = inCl > 0 && outCl > 0;
|
|
if(haveTunnels != _hadTunnels) {
|
|
if(haveTunnels) {
|
|
_statusBar.replace(StatusBar.ICON3, "Client tunnels are ready");
|
|
} else {
|
|
_statusBar.replace(StatusBar.ICON2, "Client tunnels are down");
|
|
}
|
|
_hadTunnels = haveTunnels;
|
|
}
|
|
_statusBar.update(status, details);
|
|
}
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
Util.i(this + "onBind called"
|
|
+ " Current state is: " + _state);
|
|
return _binder;
|
|
}
|
|
|
|
@Override
|
|
public boolean onUnbind(Intent intent) {
|
|
return super.onUnbind(intent);
|
|
}
|
|
|
|
// ******** following methods may be accessed from Activities and Receivers ************
|
|
/**
|
|
* @returns null if router is not running
|
|
*/
|
|
public RouterContext getRouterContext() {
|
|
RouterContext rv = _context;
|
|
if(rv == null) {
|
|
return null;
|
|
}
|
|
if(!rv.router().isAlive()) {
|
|
return null;
|
|
}
|
|
if(_state != State.RUNNING
|
|
&& _state != State.STOPPING
|
|
&& _state != State.MANUAL_STOPPING
|
|
&& _state != State.MANUAL_QUITTING
|
|
&& _state != State.NETWORK_STOPPING) {
|
|
return null;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* debug
|
|
*/
|
|
public String getState() {
|
|
return _state.toString();
|
|
}
|
|
|
|
public boolean canManualStop() {
|
|
return _state == State.WAITING || _state == State.STARTING || _state == State.RUNNING;
|
|
}
|
|
|
|
/**
|
|
* Stop and don't restart the router, but keep the service
|
|
*/
|
|
public void manualStop() {
|
|
Util.i("manualStop called"
|
|
+ " Current state is: " + _state);
|
|
synchronized(_stateLock) {
|
|
if(!canManualStop()) {
|
|
return;
|
|
}
|
|
if(_state == State.STARTING) {
|
|
_starterThread.interrupt();
|
|
}
|
|
if(_state == State.STARTING || _state == State.RUNNING) {
|
|
_statusBar.replace(StatusBar.ICON4, "Stopping I2P");
|
|
Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
|
|
stopperThread.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop the router and kill the service
|
|
*/
|
|
public void manualQuit() {
|
|
Util.i("manualQuit called"
|
|
+ " Current state is: " + _state);
|
|
synchronized(_stateLock) {
|
|
if(!canManualStop()) {
|
|
return;
|
|
}
|
|
if(_state == State.STARTING) {
|
|
_starterThread.interrupt();
|
|
}
|
|
if(_state == State.STARTING || _state == State.RUNNING) {
|
|
_statusBar.replace(StatusBar.ICON4, "Stopping I2P");
|
|
Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED));
|
|
stopperThread.start();
|
|
} else if(_state == State.WAITING) {
|
|
setState(State.MANUAL_QUITTING);
|
|
(new FinalShutdownHook()).run();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop and then spin waiting for a network connection, then restart
|
|
*/
|
|
public void networkStop() {
|
|
Util.i("networkStop called"
|
|
+ " Current state is: " + _state);
|
|
synchronized(_stateLock) {
|
|
if(_state == State.STARTING) {
|
|
_starterThread.interrupt();
|
|
}
|
|
if(_state == State.STARTING || _state == State.RUNNING) {
|
|
_statusBar.replace(StatusBar.ICON4, "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));
|
|
stopperThread.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean canManualStart() {
|
|
// We can be in INIT if we restarted after crash but previous state was not RUNNING.
|
|
return _state == State.INIT || _state == State.MANUAL_STOPPED || _state == State.STOPPED;
|
|
}
|
|
|
|
public void manualStart() {
|
|
Util.i("restart called"
|
|
+ " Current state is: " + _state);
|
|
synchronized(_stateLock) {
|
|
if(!canManualStart()) {
|
|
return;
|
|
}
|
|
_statusBar.replace(StatusBar.ICON1, "I2P is starting up");
|
|
setState(State.STARTING);
|
|
_starterThread = new Thread(new Starter());
|
|
_starterThread.start();
|
|
}
|
|
}
|
|
|
|
// ******** end methods accessed from Activities and Receivers ************
|
|
/**
|
|
* Turn off the status bar. Unregister the receiver. If we were running,
|
|
* fire up the Stopper thread.
|
|
*/
|
|
@Override
|
|
public void onDestroy() {
|
|
Util.i("onDestroy called"
|
|
+ " Current state is: " + _state);
|
|
|
|
_handler.removeCallbacks(_updater);
|
|
_statusBar.remove();
|
|
|
|
I2PReceiver rcvr = _receiver;
|
|
if(rcvr != null) {
|
|
synchronized(rcvr) {
|
|
try {
|
|
// throws if not registered
|
|
unregisterReceiver(rcvr);
|
|
} catch(IllegalArgumentException iae) {
|
|
}
|
|
//rcvr.unbindRouter();
|
|
//_receiver = null;
|
|
}
|
|
}
|
|
synchronized(_stateLock) {
|
|
if(_state == State.STARTING) {
|
|
_starterThread.interrupt();
|
|
}
|
|
if(_state == State.STARTING || _state == State.RUNNING) {
|
|
// should this be in a thread?
|
|
_statusBar.replace(StatusBar.ICON5, "I2P is shutting down");
|
|
Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED));
|
|
stopperThread.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transition to the next state. If we still have a context, shut down the
|
|
* router. Turn off the status bar. Then transition to the stop state.
|
|
*/
|
|
private class Stopper implements Runnable {
|
|
|
|
private final State nextState;
|
|
private final State stopState;
|
|
|
|
/**
|
|
* call holding statelock
|
|
*/
|
|
public Stopper(State next, State stop) {
|
|
nextState = next;
|
|
stopState = stop;
|
|
setState(next);
|
|
}
|
|
|
|
public void run() {
|
|
try {
|
|
Util.i(MARKER + this + " stopper thread"
|
|
+ " Current state is: " + _state);
|
|
RouterContext ctx = _context;
|
|
if(ctx != null) {
|
|
ctx.router().shutdown(Router.EXIT_HARD);
|
|
}
|
|
_statusBar.remove();
|
|
Util.i("********** Router shutdown complete");
|
|
synchronized(_stateLock) {
|
|
if(_state == nextState) {
|
|
setState(stopState);
|
|
}
|
|
}
|
|
} finally {
|
|
stopForeground(true);
|
|
_statusBar.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* First (early) hook. Update the status bar. Unregister the receiver.
|
|
*/
|
|
private class ShutdownHook implements Runnable {
|
|
|
|
public void run() {
|
|
Util.i(this + " shutdown hook"
|
|
+ " Current state is: " + _state);
|
|
_statusBar.replace(StatusBar.ICON5, "I2P is shutting down");
|
|
I2PReceiver rcvr = _receiver;
|
|
if(rcvr != null) {
|
|
synchronized(rcvr) {
|
|
try {
|
|
// throws if not registered
|
|
unregisterReceiver(rcvr);
|
|
} catch(IllegalArgumentException iae) {
|
|
}
|
|
//rcvr.unbindRouter();
|
|
//_receiver = null;
|
|
}
|
|
}
|
|
synchronized(_stateLock) {
|
|
// null out to release the memory
|
|
_context = null;
|
|
if(_state == State.STARTING) {
|
|
_starterThread.interrupt();
|
|
}
|
|
if(_state == State.WAITING || _state == State.STARTING
|
|
|| _state == State.RUNNING) {
|
|
setState(State.STOPPING);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Second (late) hook. Turn off the status bar. Null out the context. If we
|
|
* were stopped manually, do nothing. If we were stopped because of no
|
|
* network, start the waiter thread. If it stopped of unknown causes or from
|
|
* manualQuit(), kill the Service.
|
|
*/
|
|
private class FinalShutdownHook implements Runnable {
|
|
|
|
public void run() {
|
|
try {
|
|
Util.i(this + " final shutdown hook"
|
|
+ " Current state is: " + _state);
|
|
//I2PReceiver rcvr = _receiver;
|
|
|
|
synchronized(_stateLock) {
|
|
// null out to release the memory
|
|
_context = null;
|
|
Runtime.getRuntime().gc();
|
|
if(_state == State.STARTING) {
|
|
_starterThread.interrupt();
|
|
}
|
|
if(_state == State.MANUAL_STOPPING) {
|
|
setState(State.MANUAL_STOPPED);
|
|
} else if(_state == State.NETWORK_STOPPING) {
|
|
// start waiter handler
|
|
setState(State.WAITING);
|
|
_handler.postDelayed(new Waiter(), 10 * 1000);
|
|
} else if(_state == State.STARTING || _state == State.RUNNING
|
|
|| _state == State.STOPPING) {
|
|
Util.i(this + " died of unknown causes");
|
|
setState(State.STOPPED);
|
|
stopForeground(true);
|
|
stopSelf();
|
|
} else if(_state == State.MANUAL_QUITTING) {
|
|
setState(State.MANUAL_QUITTED);
|
|
stopForeground(true);
|
|
stopSelf();
|
|
}
|
|
}
|
|
} finally {
|
|
_statusBar.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
private State getSavedState() {
|
|
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
|
|
String stateString = prefs.getString(LAST_STATE, State.INIT.toString());
|
|
for(State s : State.values()) {
|
|
if(s.toString().equals(stateString)) {
|
|
return s;
|
|
}
|
|
}
|
|
return State.INIT;
|
|
}
|
|
|
|
private void setState(State s) {
|
|
_state = s;
|
|
saveState();
|
|
}
|
|
|
|
/**
|
|
* @return success
|
|
*/
|
|
private boolean saveState() {
|
|
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
|
|
SharedPreferences.Editor edit = prefs.edit();
|
|
edit.putString(LAST_STATE, _state.toString());
|
|
return edit.commit();
|
|
}
|
|
}
|